-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathDEVLOG
More file actions
764 lines (529 loc) · 62.2 KB
/
DEVLOG
File metadata and controls
764 lines (529 loc) · 62.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
# Development Log — GSF Website
## 2026-03-07 (cont.) - Data Layer: Schema Consolidation, Notion Roll-ups, and Smart Lookups
**Objective**: Lay the foundation for a centralised data layer — consolidate fragmented JSON files, eliminate slow N+1 Notion API calls, embed project leads into `projects.json`, and make components smarter about resolving people by name.
### What We Built
- **Schema consolidation** — collapsed 4 member files into 1: `steering-members.json`, `general-members.json`, `academic-government-members.json` (+ `team.json`) replaced by `members.json` (all members with `membershipLevel` and `active` flags) and `people.json` (all volunteers/staff/chairs). Consumers filter inline.
- **Notion UUIDs on all records** — added `id: page.id` to every exported record in `members.json`, `people.json`, `projects.json`, and `press-mentions.json`. Stable identifiers that survive name changes.
- **Roll-up-based company lookup** — eliminated `memberPageCache` (which made N sequential `notion.pages.retrieve` API calls per volunteer). Volunteers DB now has `Company Name` and `Company Website` roll-up properties read directly from the volunteer row. Fetch time: ~2.5 min → ~70 seconds.
- **Project leads in `projects.json`** — each project now has a `leads` array with `{ fullName, roleLabel, jobTitle, company, linkedin, photo }`. Built as a side-effect of subscription processing in `fetchChairsAndLeads` — zero extra API calls. Photos annotated via `personPhotoMap` built after `people.json` is assembled.
- **`role` → `jobTitle` rename** — disambiguated the field name for a person's job title at their employer. Updated fetch script and all consumers; `TeamGrid` kept backward-compatible fallback.
- **Logo marquee from `members.json`** — `LogoMarquee` now derives its logo list directly from `members.json` (filtering `active && logo && !hideLogo`), removing the `logos.json` derived file from the critical path and pre-wiring the `hideLogo` flag.
- **TeamGrid name-based lookup** — `TeamGrid` auto-resolves full person data from `people.json` when only `fullName` is passed. Explicit props take precedence; missing fields (photo, jobTitle, company, socialMedia) are filled from `people.json`. Three usage patterns now work: name-only, partial override, or full manual data.
- **Feature spec** — created `docs/features/data-layer.md` capturing the full vision: typed query functions in `src/lib/data.ts`, UUID-based props, build-time warnings, and static `/api/` endpoints for cross-microsite consumption.
### The Journey
The session started as an audit: four member JSON files existed because the Gatsby site expected them. With Astro, that constraint was gone. Consolidating into `members.json` + `people.json` meant a single source of truth and consumers filtering inline. Four pages (`about/`, `press/`, `governance/`, `policy/`) were updated to import the new files.
The `memberPageCache` performance bug surfaced while tracing fetch slowness. The original code called `notion.pages.retrieve` for every volunteer's employer — once per volunteer, sequentially. With 400+ volunteers, that's 400+ sequential API calls. The fix was a database design insight: Notion roll-up properties can pull related data directly onto the Volunteers row. Adding `Company Name` and `Company Website` roll-ups to the Volunteers DB meant the script could read company data without any extra API calls. Nearly two minutes dropped off the fetch time.
Project leads were an elegant derivation. The script already iterates every subscription row to build the people list — subscriptions are how volunteers are assigned to projects. Capturing lead-role subscriptions into a `pwciLeadsMap` as a side-effect cost nothing. By the time `fetchProjects` ran, the map was ready. The `jobTitle` field was added shortly after when the user noticed it was missing from the lead records — a one-line addition to the lead-building loop.
The logo marquee bug was a small discovery: `logos.json` had somehow ended up empty despite the fetch completing. Rather than debug the derived file, removing the indirection was cleaner — have the component read `members.json` directly. Same data, one less file to keep in sync.
TeamGrid name-lookup was the most satisfying piece: standards pages had hardcoded person objects with duplicated photo paths, company names, and LinkedIn URLs. With the lookup, `{ fullName: "Chris Adams" }` fills itself from `people.json`. Partial overrides work too — pass a `role` badge label and the component merges it with resolved data.
### Technical Achievements
- N+1 API call pattern eliminated — `notion.pages.retrieve` removed from hot path entirely
- Fetch time: ~2.5 min → ~70 seconds
- 4 JSON files → 2 JSON files; all consumers updated
- `projects.json` contains embedded `leads` arrays with full person data
- Stable Notion UUIDs on every record in every data file
- `TeamGrid` resolves full person data from just a name
### Key Learnings
- **Design databases for read patterns**: Notion roll-ups let you pull related data onto the row you're already reading — no separate API call needed. When data is slow to fetch, look at what related records you're resolving and whether a roll-up could bring that data home.
- **Derived files create drift risk**: `logos.json` as a derived view of members created a subtle maintenance surface. Having the component derive its own view at build time removes a file from the critical path.
- **Build-time maps are cheap**: `personPhotoMap`, `personByName`, `pwciLeadsMap` are all built by iterating already-fetched data. Build lookup maps liberally — they cost nothing at build time.
### Statistics
- Fetch script: 247 insertions, 142 deletions
- 4 JSON files consolidated into 2
- ~400 sequential Notion API calls eliminated
- Fetch time: ~2.5 min → ~70 seconds
- 4 consumer pages updated (about, press, governance, policy)
### Next Steps
- **Hide Logo checkbox** — `hideLogo: boolean` in `members.json`, filter from logo marquee (hook already in place)
- **`src/lib/data.ts`** — typed query functions wrapping JSON files; UUID-based lookups; build-time warnings for unresolved IDs
- **Static `/api/` endpoints** — serve data files from Netlify CDN for cross-microsite consumption
## 2026-03-07 (cont.) - Standards Pages: Dynamic Data, Hero Badges & TeamGrid Refinements
**Objective**: Wire up all standards project pages (RTC, SCI, SCI-AI, SOFT, WDPC) to pull team and metadata from `projects.json`, add working group and lifecycle stage badges to heroes, and simplify TeamGrid usage with the `people.json` auto-resolve pattern.
### What We Built
- **Dynamic `projects.json` integration** across all 5 standards pages — each page now imports `projects.json`, finds its project by slug, and resolves the parent working group dynamically
- **Hero `badges` prop** — new array-based badge system on the Hero component supporting multiple badges with variants:
- Working group badge (green `bg-accent-lighter`): e.g. "A Software Standards Working Group Project"
- Lifecycle stage badge (teal `bg-primary-light`): e.g. "Published", "Ratified", "Draft"
- **TeamGrid driven from projects.json** — all pages now pass just `fullName` and `role` (roleLabel), with the component's `resolveMember` function auto-filling photos, jobTitles, companies, and LinkedIn from `people.json`
- **TeamGrid card sizing fix** — cards now have consistent `max-w-xs` (320px) width with `mx-auto`, and single-member layouts use `flex justify-center` to centre the card
- **ResourceCards `columns` prop** — new prop (2|3|4) for flexible grid layouts, used on RTC page for 4-column resources
- **Hero `badge` prop** — single badge support added (later superseded by `badges` array)
- **WDPC leadership text fix** — corrected "Hardware Standards Working Group" to "WDPC Project Lead" for My Truong
### Pages Updated
| Page | Slug | Lifecycle | Parent WG | Leads |
|------|------|-----------|-----------|-------|
| SCI | `sci` | Published | Software Standards WG | Navveen Balani, Henry Richardson |
| SCI-AI | `sci-ai` | Ratified | Software Standards WG | Navveen Balani, Henry Richardson |
| SOFT | `soft` | Ratified | Software Standards WG | Pindy Bhullar |
| WDPC | `wdpc` | Draft | Hardware Standards WG | My Truong (custom section) |
| RTC | `real-time-cloud` | Published | Software Standards WG | Pindy Bhullar, Adrian Cockcroft |
### The Journey
The session started with the RTC page — the first to be wired up to `projects.json`. The user had already added `jobTitle` fields to all leads in the data and updated TeamGrid with a `resolveMember` function that auto-fills member details from `people.json` by name lookup.
The initial TeamGrid mapping was verbose — 7 fields manually extracted from each lead. After confirming that both RTC leads (Pindy Bhullar, Adrian Cockcroft) existed in `people.json` with complete data, we simplified to just 2 fields: `fullName` and `role`. The component handles the rest.
Rolling this pattern out to the other 4 pages was mostly mechanical, but each had nuances:
- **SCI and SCI-AI** shared the same two leads (Navveen, Henry) — straightforward
- **SOFT** only had Pindy in `projects.json` despite the page previously showing Sean O'Keefe too — the data source became the source of truth
- **WDPC** had a custom "Leading the Charge" section with decorative SVGs, a recruitment badge, and an illustration — the user asked to keep this rather than replace it with TeamGrid
The hero badge colour went through several iterations: started with `bg-primary-darker` (too dark), then `bg-primary-dark` (still dark), then `bg-primary-light text-primary-darker` (text clashed), finally settled on `bg-primary-light text-white` — a medium teal that's visually distinct from the green working group badge while staying on-brand.
The TeamGrid card sizing issue surfaced when comparing cards across pages — different grid containers produced different card widths. Adding `max-w-xs mx-auto` to each card and special-casing single-member layouts with `flex justify-center` resolved the inconsistency.
### Key Learnings
- **`people.json` auto-resolve is powerful** — pages only need to pass `fullName` and any project-specific fields (like `role`/`roleLabel`). Photos, companies, job titles, and social links are all resolved automatically
- **Data source becomes truth** — when SOFT's `projects.json` only lists one lead, the page shows one lead. This is correct behaviour — if someone's missing, update the data, not the page
- **Badge colour iteration** — on a cream background, teal badges need white text for contrast. Dark text on medium teal clashes visually
### Statistics
- 7 components modified (hero, team-grid, resource-cards, plus 4 other pages)
- 5 standards pages updated with dynamic data
- TeamGrid member mapping reduced from 7 fields to 2 per lead
- 3 badge colour iterations before settling on final design
### Next Steps
- Build SEE and SCI-Web standards pages
- Consider whether WDPC should also use TeamGrid (once more leads are added to projects.json)
- Roll out badges pattern to non-standards project pages
## 2026-03-07 (cont.) - Astrobook Component Playground
**Objective**: Set up a visual component playground for browsing and testing all parameterised Astro components, and fix rendering errors in story files.
### What We Built
- **Astrobook integration** — installed and configured `astrobook` as an Astro integration, available at `/playground/`
- Configured with project's `global.css` for correct Tailwind tokens and fonts
- Title: "GSF Component Playground"
- Scans `src/components/` for `.stories.ts` files
- **24 story files** covering all parameterised components with **51 total story variants**:
- hero (3), text-block (4), feature-grid (4), cta-banner (2), testimonial (2), card-grid (2), community-reach (2), stats-grid (2), resource-cards (1), cta-card (2), text-with-image (4), tabbed-section (2), logo-marquee (3), timeline (1), vertical-pipeline (1), team-grid (2), split-cards (1), footer (1), navbar (3), breadcrumb (2), newsletter-signup (2), governance-diagram (2), member-stories (1), article-carousel (2)
- **4 story file fixes** — prop name mismatches causing TypeErrors:
- `team-grid.stories.ts` — `name` → `fullName`, removed non-existent `photoSrc`
- `timeline.stories.ts` — removed non-existent `title` field, embedded titles in `description` via `<strong>` tags
- `vertical-pipeline.stories.ts` — `label` → `name`
- `split-cards.stories.ts` — added missing `role` to attribution object
### The Journey
Researched component catalogue solutions for the Astro/React/Tailwind/Radix/shadcn stack. Storybook doesn't support Astro components (only React/Vue). Histoire supports Vue/Svelte but not Astro. Astrobook is a minimal Storybook alternative built specifically for Astro — it renders `.astro` components natively using CSF v3 story format.
After installing and creating all 24 story files, the user tested the playground and hit a TypeError at `team-grid.astro:131` — `Cannot read properties of undefined (reading 'split')`. This was because the story used `name` instead of `fullName` (the correct prop per the `TeamMember` interface). The component calls `fullName.split(' ')` to generate initials, so `undefined.split()` crashed.
Proactively audited all other story files against their component TypeScript interfaces and found 3 more mismatches. After fixing all 4, verified every story renders correctly in the browser.
### Key Learnings
- **Astrobook** is currently the only viable option for previewing Astro components — Storybook can't render `.astro` files
- **Story prop names must exactly match component interfaces** — CSF v3 `args` are passed directly as props, so a mismatch like `name` vs `fullName` causes runtime crashes
- **Proactive auditing pays off** — checking all stories against interfaces after finding one bug caught 3 more before the user hit them
### Statistics
- 1 new dependency (astrobook)
- 24 story files created
- 51 story variants
- 4 prop mismatch bugs fixed
- All components rendering successfully at `/playground/`
## 2026-03-07 - Community, Brand & Assets Landing Pages
**Objective**: Build the Community and Brand & Assets landing pages — two of the remaining "bigger pieces" needed to complete the site's main navigation structure.
### What We Built
- **Community page** (`/community/`) — full landing page for GSF's community programmes:
- Hero with Movement Platform and Champions CTAs
- CommunityReach stats block (450K podcast downloads, 12K+ meetup members, 12K LinkedIn followers, 7.8K newsletter subscribers, 37 meetup groups)
- Listen & Learn section with large podcast cards for Environment Variables and CXO Bytes (custom HTML, not component — needed `aspect-video` image treatment)
- Connect section (FeatureGrid: Movement Platform, Champions, Newsletter, Earn Badges)
- Global Meetup Network (world map left, stats and CTAs right — custom two-column layout)
- Events section (FeatureGrid: Global Summit, Carbon Hack)
- Article carousel filtered by `community` tag (10 articles tagged)
- CTA banner driving to Movement Platform
- **Brand & Assets page** (`/brand/`) — resource hub for brand guidelines:
- Hero with brand guidelines PDF download and trademark policy CTAs
- "Using our brand" intro (TextBlock)
- Colour palette section with swatches rendered from exact PDF hex codes:
- Primary (Teal): 7 shades from #003834 to #D9E7E7
- Accent (Green): 7 shades from #849E2A to #F2F6E9
- Grey: 7 shades from #000000 to #C3C3C3
- Accent highlights: Orange (#D87814) and Cyan (#00E7FF)
- Prominent callout box emphasising these are just highlights — full guidelines cover ratios, logo rules, etc.
- Typography showcase (Nunito Sans, weights 200–900)
- Logo preview (full, vertical, icon variants)
- Download Assets grid (6 cards: Logos, Icons, Illustrations, Presentations, Our Work, All Brand Assets — all linking to Google Drive)
- Canva Templates section (4 meetup templates, 2 social templates, plus meetup and champions Google Drive folders)
- Official description block (approved boilerplate text)
- Contact CTA (help@greensoftware.foundation)
### Supporting Work
- **10 articles tagged** with `community` (and some also `events`) for the community page carousel
- **2 articles untagged** that didn't fit the community narrative (ClimateAction.tech interview, Chris Lloyd-Jones interview)
- **2 podcast images** copied to `public/assets/` (environment-variables-podcast.png, cxo-bytes-podcast.png)
- **Colour palette corrected** — initially used CSS token values from `global.css`, but user pointed out they didn't match the brand guidelines PDF. Re-read the PDF and updated all hex codes to match exactly.
### The Journey
The community page went through several layout iterations based on user feedback:
1. Started with a TabbedSection approach — user preferred distinct sections instead
2. Meetup Network section tried three positions: after hero (too similar to hero layout), after Listen & Learn, finally settled under Connect
3. CommunityReach stats moved from lower on the page to right after the hero for maximum impact
4. Podcast cards needed large images — used custom HTML with `aspect-video` rather than a component
The brand page required research from two Notion sources: Brand & Trademark Policy and Design Resources. The Design Resources page had a structured library with Google Drive links and Canva template URLs.
The biggest correction was the colour palette. The initial version used the CSS custom properties from `global.css` (which are the site's implementation tokens), but the user noticed they didn't match the brand guidelines PDF. Reading the actual PDF (GSF-guidelines-v03-basics.pdf) revealed quite different values — particularly the greys, which go from true black (#000000) through proper mid-greys (#3C3C3C, #595959, #808080) rather than the site's more limited grey tokens.
### Key Learnings
- **Brand PDF colours differ from CSS tokens** — the website's `global.css` tokens are an implementation of the brand guidelines, not a 1:1 copy. The brand page should show the official PDF values, not the CSS approximations.
- **Inline `style` for exact colours** — switched from Tailwind `bg-*` classes to `style={`background-color: ${hex}`}` so the swatches show the exact branded hex values rather than the nearest CSS token.
- **Community page article filtering** — tag-based filtering (`tags.includes("community")`) works well for curating content across pages. Tags can be combined (e.g. "community" + "events") for articles that appear in multiple carousels.
### Statistics
- 2 new pages created (community/index.astro, brand/index.astro)
- 10 articles tagged for community carousel
- 2 podcast images added
- Colour palette updated to match brand PDF (4 palettes, 23 swatches total)
### Next Steps
- Better podcast thumbnail images for Environment Variables and CXO Bytes (user providing)
- Google Analytics integration
- Newsletter page/form
- Roll out to greensoftware.org
## 2026-03-07 (cont.) - Standards Page, Nav Restructure, and Email Consolidation
**Objective**: Build the `/standards/` process page, restructure navigation menus across all four top-level items, update the homepage "What we do" section, and consolidate contact emails.
### What We Built
- **Standards page** (`/standards/`) — full process showcase:
- Hero with AI-facilitated consensus messaging
- VerticalPipeline showing the 7-stage specification lifecycle (Proposal → Published)
- Dynamic project cards from `projects.json` with lifecycle stage badges and `urlSlug` overrides (e.g. `real-time-cloud` → `rtc`)
- "Learn more" links hidden for Proposal/Pre-proposal stage projects
- AI-facilitated consensus and Assemblies TextWithImage sections
- FeatureGrid: 8 characteristics of good specifications (bordered, 2-column)
- SCI Certification CTACard and final CTABanner
- **Navigation restructure** across all four menus:
- Standards: RTC moved from Hardware to Software; SWE removed
- Adoption: "Awesome Green Software" → "Resource Catalogue"; Manifesto and Policy Engagement removed; per-section `headerLink` buttons added to Education and Policy & Research
- Community: Articles removed from "Listen & Learn"; section renamed to "Listen"
- About: History link removed from Organisation
- All menus: all icons (`iconSrc` and `icon`) commented out to evaluate appearance without them
- **headerLink feature** — new `NavSection` property rendering section-level CTA buttons in both desktop mega-menu and mobile accordion nav
- **Homepage "What we do"** — four pillars updated to: Standards, Policy & Research, Education, Community
- **Email consolidation** — `news@greensoftware.foundation` changed to `help@greensoftware.foundation` on press page and brand page. `meetup@` left unchanged.
### The Journey
The standards page went through significant iteration. The AI consensus section initially followed the hero but felt too abrupt — it was moved below the lifecycle pipeline and project cards. The cards evolved from FeatureGrid to CardGrid and finally to custom markup to support lifecycle badges and conditional links.
Navigation restructure was driven by content strategy: removing items without standalone pages (History), adding headerLink buttons to point users to programme landing pages from within mega-menus.
The email audit revealed 3 structural emails (`help@`, `news@`, `meetup@`) plus ~10 historical personal emails in article content. `news@` was consolidated to `help@` since it wasn't actively maintained.
### Key Learnings
- **Per-section CTA buttons** (`headerLink`) work better than a single footer link when a mega-menu has distinct programmes each deserving their own landing page
- **Comment out rather than delete** icon properties — allows easy A/B comparison and re-enabling
- **Email consolidation** — auditing `mailto:` links across the whole site reveals scattered addresses that may not be monitored
### Statistics
- 1 new page (standards/index.astro)
- 1 new NavSection property (headerLink)
- ~8 files modified for nav restructure
- 2 email references consolidated (news@ → help@)
- All icons commented out across 4 nav menus (~30 items)
## 2026-03-06 - About Page
**Objective**: Research, spec, and build the About page (`/about/`) — the foundation's primary "who we are" page.
### What We Built
- **About page** (`src/pages/about/index.astro`) — a 10-section page composed from the parameterised component library, pulling content from the legacy manifesto, data files, and existing site messaging
- **Feature spec** (`docs/features/about-page.md`) — comprehensive spec documenting all content sources, proposed structure, and open questions
- **Manifesto SVG migration** — 5 value illustrations copied from the legacy Gatsby site to `public/assets/values-*.svg`
### Page Structure (final)
1. Hero — "About the Green Software Foundation" with elevator pitch
2. Mission & Vision — side-by-side, from the official manifesto (kept verbatim)
3. What is Green Software? — updated definition broadening beyond carbon to all environmental impacts (energy, water, hardware lifecycle, the full digital ecosystem)
4. Founded in 2021 — founding story (Accenture, GitHub, Microsoft, ThoughtWorks) with CTA to governance
5. Our Values — 4 values in 2x2 grid with migrated manifesto SVGs (removed "Minimise Carbon" as no longer reflective of the broader mission)
6. How We're Structured — nonprofit under Linux Foundation, consensus-based governance, CTA to `/governance/`
7. What We Do — 4 pillars (Standards, Policy, Education, Research) in bordered grid
8. By the Numbers — 6 stats in centred 3-column grid, member count from data files
9. Logo Marquee — 60+ member logos
10. CTA — "Want to shape the future of green software?" → membership
### The Journey
Started with a broad research phase — launched four parallel agents to scan the existing production site, search Notion, explore the codebase, and check additional URL paths. No About page existed on either the old or new site despite nav/footer linking to `/about/`. Notion had no dedicated About content — the legacy manifesto markdown was the primary source.
The user directed several refinements: removed "Read the manifesto" CTA, updated the green software definition to cover all environmental impacts and the full digital ecosystem (not just software carbon), removed "Minimise Carbon" from values, centred the stats section, and moved "How we're structured" above "What we do" for better narrative flow.
### Key Learnings
- **TextWithImage and StatsGrid use `heading`/`headingAccent` props**, not the `*accent*` syntax
- **The green software definition is evolving** — now covers carbon, energy, water, hardware across the entire digital ecosystem
- **"Minimise Carbon" is being retired as a core value** — reflects broadening beyond carbon
### Statistics
- 1 new page (~278 lines), 1 feature spec, 5 SVGs migrated, 4 parallel research agents
## 2026-03-06 - Policy & Research Page
**Objective**: Build the Policy & Research landing page — covering the Policy Working Group, Policy Radar, consultation responses, partnerships, research publications, and engagement principles.
### What We Built
- **Policy & Research page** (`/policy/`) — composed from parameterised components:
- Hero, TextBlock intro with mission statement link to PWG GitHub
- Leadership section (Chris Adams, Aya Saed co-chairs; Joseph Cook Head of Research) with team.json photos
- Policy Radar (TextWithImage) linking to policy-radar.greensoftware.foundation
- Policy Engagement (`#engagement`) — 6 cards: GHG Protocol Scope 2, AI Environmental Impacts Act, NY CCAA, EGDC, UK GDSA, EU AI Act — all with article links
- Engagement Principles (FeatureGrid) — 5 principles: Legitimacy, Opportunity, Consistency, Transparency, Accountability
- Partnerships (FeatureGrid) — W3C, IASA, SustainableIT.org, UK GDSA, BCS
- Research — 4 publication cards: AI Environmental Assessments, Green AI Position Paper, SCI for Foundation Models (Texas State), Carbon-Aware Computing (UBS & Microsoft)
- Featured Aya Saed quote, article carousel, CTA, Footer
- **Article tags infrastructure** — added `tags` field to articles schema and Sveltia CMS config. Tagged 12 articles `"policy"`, 3 `"research"`. Carousel dynamically filters by tags.
### Key Learnings
- FeatureGrid `icon` expects SVG file paths, not Lucide icon names
- Tags are non-breaking — optional array, existing articles unaffected
- NY CCAA and EGDC lack dedicated articles — linked to PWG mission statement article instead
### Statistics
- 1 new page (~310 lines), 15 articles tagged, 2 config files updated
## 2026-03-05/06 - PageFind Search Integration & Notion Team Fix
**Objective**: Fix the broken governance team data in the Notion fetch script, then implement site-wide search using PageFind so the existing navbar search button actually works.
### What We Built
- **Notion fetch fix** — fixed two bugs causing 0 team members: `Person` field is `title` type (not `rich_text`) and `Role` is `rich_text` (not `title`); added direct photo download from GSF Team DB before falling back to volunteer lookup
- **PageFind search** — full static site search with a custom React search dialog:
- Custom `search-dialog.tsx` using Radix Dialog + PageFind JS API
- Lazy-loaded via `React.lazy()` so PageFind JS only loads when search opens
- Keyboard shortcut: `Cmd+K` / `Ctrl+K` to open, `Esc` to close
- Phrase search by default for multi-word queries (falls back to OR if no results)
- Content type badges (article/story) on each result
- Highlighted excerpts showing matched terms in context
- **Opt-in indexing model** — only pages with `data-pagefind-body` are indexed:
- Individual English article pages
- Individual story pages
- Listing pages, homepage, non-English articles all excluded
- **Chrome exclusion** — `data-pagefind-ignore` on navbar, footer, CTA banner, newsletter signup
### The Journey
The session started with a quick Notion fix. The governance page was showing 0 team members because the fetch script had swapped the Notion property types — `Person` is actually a `title` field and `Role` is `rich_text`, opposite of what the code assumed. A quick API inspection confirmed the types, and the fix was straightforward. Also added direct photo extraction from the GSF Team DB's own Photo property, since the previous code only looked up photos via the Volunteers DB.
The main work was PageFind integration. The navbar already had a search icon button as a placeholder — the task was to make it functional. Rather than using PageFind's default UI (which isn't customisable enough for the site's brand), we built a custom React dialog using PageFind's JS API.
The trickiest part was **Vite compatibility**. PageFind generates its index at build time in `dist/pagefind/`, and the JS needs to be loaded at runtime from `/pagefind/pagefind.js`. But Vite's import analysis tries to bundle this at build time, causing Rollup errors. Three approaches were tried:
1. `/* @vite-ignore */` comment — didn't work
2. `rollupOptions.external` — fixed build but broke dev server
3. `new Function("return import('/pagefind/pagefind.js')")` — works perfectly in both dev and build by completely bypassing Vite's static analysis
The user then requested three refinements:
- **Phrase search**: multi-word queries now wrap in quotes for exact phrase matching, with automatic fallback to OR if the phrase returns no results
- **No listing pages**: switched from default "index everything" to opt-in `data-pagefind-body`, naturally excluding listing pages, homepage, and navigation
- **English only**: non-English articles excluded by checking `article.id.startsWith("en/")` rather than the `lang` field (which defaults to "en" for all articles)
### Technical Achievements
- PageFind indexes 198 pages (down from 234 after filtering)
- Zero-JS search button until clicked (lazy loading)
- Vite-compatible PageFind loading pattern that works in both dev and production
- Phrase search with graceful degradation to OR matching
### Key Learnings
- **Vite import analysis is aggressive** — even dynamic `import()` with `/* @vite-ignore */` can be caught. The `new Function()` wrapper is the reliable escape hatch
- **PageFind's opt-in model is cleaner** — adding `data-pagefind-body` to any page switches PageFind to opt-in mode globally, which naturally excludes listing pages, the homepage, and other non-content pages
- **Notion property types matter** — `title` and `rich_text` are different field types with different extraction methods. Always inspect the actual API response when debugging data issues
- **Content collection `lang` field pitfalls** — the schema defaults `lang` to `"en"`, so non-English articles also report `lang === "en"`. Checking the file path (`article.id.startsWith("en/")`) is more reliable
### Statistics
- 2 bugs fixed in Notion fetch script
- 1 new React component (search-dialog.tsx, ~180 lines)
- 8 existing files modified for PageFind integration
- 198 pages indexed by PageFind
### Next Steps
- Consider adding `data-pagefind-body` to static pages as they're built
- Mobile search in the hamburger menu
- Research pages indexing once that collection exists
## 2026-03-05 - Press Page, Nav Link Updates, and World Map
**Objective**: Build a comprehensive Press & Media page pulling dynamic data from multiple sources, update navigation links across the site, and replace the homepage world map placeholder with the final SVG from the designer.
### What We Built
- **Press & Media page** (`/press/`) — a 362-line page pulling data dynamically from JSON files, content collections, and Notion. Sections: hero, about/boilerplate, impact stories, leadership, key messages, 22-item timeline, media coverage (54 press mentions), media contact, design resources, and CTA
- **Nav link updates** — corrected URLs for CXO Bytes podcast (→ wiki), Movement Platform (renamed from Community Platform), Carbon Hack (→ hack.greensoftware.foundation), Member Stories (renamed from Success Stories), Press & Media (→ /press/), State of Green Software (→ stateof.greensoftware.foundation)
- **Nav dropdown footer labels** — renamed to "About our standards process →", "About our policy & research programme →", "About our community programme →"
- **Homepage world map** — replaced placeholder SVG with final design from Genya (two-colour SVG: #adcb53 green dots, #00544f teal continents, transparent background)
- **Press mentions Notion integration** — added `fetchPressMentions()` to the Notion fetch script, pulling 54 entries from the "GSF Mentions in the News" database into `press-mentions.json`
### The Journey
The session covered three distinct workstreams. Navigation link updates came first — straightforward URL corrections and label renames. One misstep: initially changed top-level nav labels (Standards → "Our Standards") when the user actually wanted the footer link *buttons inside the dropdowns* changed. Quickly reverted.
The world map swap was simple — the designer's SVG uses transparent background so the section's dark teal shows through naturally.
The press page was the main piece. Started by reading the GSF Media Kit from Notion to understand what content was available. Key design decisions:
- **Pull data dynamically** to avoid staleness — member counts computed from JSON files, governed-by list from active steering members, stories from content collection
- **Leadership** — initially pulled from team.json by role, but discovered the data was out of date (had Chris Lloyd-Jones as Vice-Chair when it's actually Jonathan Turnbull from Google). Switched to a hardcoded `leadershipConfig` array at the top of the file for easy updating
- **Timeline** — 22 milestone entries from GSF founding (May 2021) through SCI for AI ratification (Dec 2025), with links to articles and external sites
- **Media coverage** — tried `@astro-community/astro-embed-link-preview` for OG unfurling but many sites (Forbes, SDxCentral, etc.) returned 403 Forbidden at build time. Reverted to simple card layout with source name, date, title, and domain
### Technical Achievements
- Press page pulls from 5 data sources: 3 member JSONs, team.json, press-mentions.json, stories content collection
- Active member filtering (72 active across 3 tiers: 6 steering, 47 general, 19 academic/government)
- Notion fetch script now exports press mentions alongside members, team, and projects
### Key Learnings
- **OG unfurling at build time is unreliable** — many news sites block server-side requests with 403s. Simple card layouts with structured data are more robust
- **Leadership data goes stale** — hardcoded config with names/titles at the top of the file is better than relying on role fields in team.json that may not be updated
- **Active vs inactive members** — the `active` boolean in member JSONs is essential for accurate counts; BCG, Avanade, and Intel are inactive steering members
### Statistics
- 1 new page (press/index.astro — 362 lines)
- 1 new data file (press-mentions.json — 54 entries)
- 1 new asset (world-map.svg)
- ~8 files modified for nav link updates
- CLAUDE.md updated with press page documentation
### Next Steps
- Google Analytics integration
- Brand & Assets page (linked from press page design resources)
- Newsletter page/form
- New content pages: Standards, Policy, Research, About, Education, Community
### Reflections
The press page highlighted the tension between dynamic data (stays fresh) and hardcoded content (stays correct). Member counts and press mentions update automatically via the Notion fetch script, but leadership names and timeline milestones are necessarily manual. The right approach is to make the manual parts easy to find and edit — hence the `leadershipConfig` array at the top of the file rather than buried in JSX.
## 2026-03-05 - Nav Mega Menu: Project Icons, Dropdown Fix, and Footer Attribution
**Objective**: Wire Notion PWCI project icons into the navigation mega menu, fix dropdown overflow on narrow screens, and add Linux Foundation attribution to the footer.
### What We Built
- **PWCI project data export** — new `fetchProjects()` function in the Notion fetch script that extracts all projects, working groups, committees, and initiatives from the PWCI table, including page-level icons (both `file` and `custom_emoji` types)
- **Dynamic nav icon resolution** — rewrote `nav-items.ts` to import `projects.json` and build a slug-to-icon lookup map, so nav menu icons auto-update when Notion data changes
- **39 project icons downloaded** to `public/assets/project-icons/` (SVG and PNG mix)
- **Nav dropdown overflow fix** — added `static` positioning to `NavigationMenuItem` so dropdowns position relative to the NavigationMenu root, preventing off-screen overflow on narrow desktop viewports
- **Icon sizing system** — project icons render in 32×32 containers, Lucide fallback icons in 32×16 containers, keeping text alignment consistent across both types
- **Linux Foundation footer attribution** — added "The Joint Development Foundation Projects, LLC is an affiliate of the Linux Foundation." at the bottom of the footer
### The Journey
The session started with a nav dropdown overflow bug — on narrower desktop screens, the mega menu panels were going off-screen to the right. The fix was elegant: adding `className="static"` to the dropdown `NavigationMenuItem` changes the positioning context so dropdowns align relative to the `NavigationMenu` root rather than the individual trigger, plus `max-w-[calc(100vw-2rem)]` as a safety net.
Next came the PWCI icon export. The Notion PWCI table stores page-level icons in two formats: standard `file` uploads and `custom_emoji` (custom uploaded SVGs). The initial fetch only handled `file` types, yielding 28 icons. After the user noticed several projects (WDPC, SWE, SEE, Policy Radar) were missing, we discovered the `custom_emoji` type and added support, bringing the total to 39 icons.
The icon sizing went through several iterations based on user feedback — from size-4 ("too small") to size-8 ("absolutely awful") to the final solution: fixed-width containers (w-8) for both icon types, with Lucide icons at size-4 inside a 32×16 container and project icons in a 32×32 container.
### Key Learnings
- **Notion page icons have three types**: `file`, `custom_emoji`, and `emoji` — must handle all three for complete coverage
- **CSS `static` positioning on NavigationMenuItem** is the clean fix for Radix NavigationMenu dropdown overflow — it changes the positioning context without breaking the menu behaviour
- **Fixed-width icon containers** solve alignment issues better than trying to make different icon types the same size
### Statistics
- 39 project icons downloaded from Notion
- 47 projects exported to projects.json
- 6 files modified, 1 new directory added
### Next Steps
- Press & Media content page
- Brand & Assets URL fix
- Newsletter page/form
## 2026-03-04 - Sveltia CMS: Co-located Images, i18n, and Editorial Workflow
**Objective**: Get Sveltia CMS fully working for content editors — image previews, article management, translations, and a clear publishing workflow.
### What We Built
- **Article co-location migration** — all 195 articles moved from flat `slug.md` to `slug/index.md` with images living alongside the markdown file in `src/content/articles/en/slug/`
- **Astro image() schema** — `mainImage` field upgraded from a plain string to Astro's `image()` helper, enabling build-time image optimisation (964 images → WebP)
- **Dynamic CMS config** — replaced static `public/admin/` files with a proper Astro page (`src/pages/admin/index.astro`) and a dynamic API endpoint (`src/pages/admin/config.yml.ts`)
- **i18n support** — articles, research, and stories now support four locales (EN/JA/PT/ZH) via Sveltia's `multiple_folders` structure, with opt-in translations per entry
- **CMS list view improvements** — date-ordered lists, image thumbnails, and a `date — title` summary template across all collections
- **README documentation** — full editorial guide covering CMS login, publishing workflow, local dev setup, how translations work, why some fields are locked, and how to add new languages
### The Journey
The session started with a deceptively simple problem: images in Sveltia CMS were showing as file icons instead of previews. The image paths (`/assets/articles/slug/main.png`) didn't match what the CMS config expected, and attempts to patch the `public_folder` setting kept falling short.
The real breakthrough came from comparing against the zaneteknits project — a working Sveltia setup that used a completely different approach: **co-located images**. Instead of a separate `public/assets/articles/` tree, each article gets its own folder with images sitting right alongside the markdown. This is cleaner, more portable, and maps perfectly to how Sveltia's image widget works.
Rather than maintain the old flat structure alongside a new one, the decision was made to migrate everything in one go. A migration script (`scripts/migrate-article-images.mjs`) handled the restructure: 195 articles across four language folders, finding all image references per article, copying from `public/` to `src/content/`, and rewriting paths from `/assets/articles/slug/main.png` to `./main.png`. It ran cleanly — 195 articles, 281 images, zero missing.
With images co-located, the Astro schema needed updating. The `mainImage` field changed from `z.string().optional()` to Astro's `image()` helper, which processes images at build time into optimised WebP. This cascaded into the article pages — `<img>` tags became `<Image>` components with `widths` and `sizes` for responsive delivery. Build confirmed: 964 images processed.
Then came the CMS admin restructure. The original static `public/admin/index.html` and `config.yml` were replaced with proper Astro routes, matching the zaneteknits pattern. The config became a TypeScript API endpoint, dynamically generated per environment.
**The biggest surprise of the session:** discovering that `local_backend: true` doesn't actually work in Sveltia CMS. Unlike Decap/Netlify CMS, Sveltia doesn't use a proxy server at all — it uses the browser's **File System Access API**. The setting had been silently ignored, causing the CMS to fall back to reading from GitHub. Since the article migration hadn't been committed yet, GitHub still had the old flat format — hence articles showing as empty. The fix: remove `local_backend: true`, and instruct editors to click "Work with Local Repository" in Chrome or Edge instead.
Once articles were visible, the next step was i18n. The site already had articles in `en/`, `ja/`, `pt/`, and `zh/` subfolders, which is exactly Sveltia's `multiple_folders` structure. The config update was mostly mechanical: point each collection `folder` at the parent directory (not `en/`), add `i18n: true`, and set field-level i18n to either `true` (translatable) or `duplicate` (copied from English, locked in other locales). Fields like `date`, `mainImage`, and `organizations` are `duplicate` — a Japanese article should have the same publication date as its English counterpart. Fields like `title`, `summary`, and `body` are fully translatable. The `initial_locales: [en]` setting means translations are opt-in: editors only see the EN tab by default, and explicitly enable other locales when they want to create a translation.
### Technical Achievements
- **Zero-downtime migration**: the migration script restructured 195 articles in-place with no data loss and no broken references
- **Build-time image optimisation**: upgrading to `image()` schema unlocked Astro's image pipeline — 964 images now served as optimised WebP with responsive srcsets
- **Correct Sveltia local dev pattern**: identified and documented the File System Access API approach, removing a misconception about proxy servers that had been blocking local editorial work
- **Clean i18n architecture**: `multiple_folders` structure aligns perfectly with the existing content layout, meaning no content restructuring was needed for any of the four languages
### Key Learnings
- **Sveltia ≠ Decap CMS for local dev**: `local_backend: true` is not supported. Sveltia uses the browser's File System Access API. Always use Chrome or Edge, click "Work with Local Repository". This is documented in README now.
- **Co-location is the right default**: storing images alongside their article makes the content self-contained, portable, and directly editable. The alternative (a separate `public/assets/` tree) creates a maintenance burden and confuses the CMS.
- **Check what the CMS is actually reading**: when Sveltia showed articles as empty, the instinct was to fix the config. The real issue was that it was reading from GitHub (not local files), and GitHub had old data. Understanding the data source is the first debugging step.
- **`i18n: duplicate` vs `i18n: true`**: getting field-level i18n right matters. Structural metadata (date, image, organisations) should be `duplicate` so it stays consistent across locales. Text content should be `true` so it can be independently translated.
### Statistics
- Articles migrated: 195 (across en/ja/pt/zh folders)
- Images moved into content: 281
- Build-time images processed: 964
- Files changed in this commit: ~402 (195 new co-located articles, 195 old flat articles removed, plus config/schema/page updates)
- Net lines: +149 insertions, −12,864 deletions (mostly image paths simplified from absolute to relative)
### Next Steps
- Commit the full migration to `dev` and merge to `main` when ready for production
- Test the CMS in Chrome with "Work with Local Repository" end-to-end
- Consider adding a `zh-tw` or other locale if needed (one-line config change)
- Review whether the `lang` hidden field in frontmatter can be retired now that locale is determined by folder structure
### Reflections
This session had a satisfying arc: what looked like a CMS configuration problem turned out to be an architectural one (image co-location), and what looked like a Sveltia config problem turned out to be a fundamental misunderstanding of how it handles local development. Both required stepping back and asking "what is the system actually doing?" rather than tweaking config values. The zaneteknits reference project was invaluable — having a working example to compare against short-circuited a lot of guesswork.
## 2026-02-27 - Content Migration: Articles, Cleanup, and Article Listing
**Objective**: Complete Phase 1 of the Content Pages Migration feature — migrate all legacy articles into Astro content collections, build article detail and listing pages, and clean up the legacy markdown.
### What We Built
- **Astro content collections** for articles (195 files) and pages (4 files) with full Zod schemas
- **Article detail page** (`/articles/en/[slug]`) with breadcrumbs, author bios, translators, hero images, date/origin metadata, SEO meta tags, JSON-LD structured data, newsletter signup, and CTA banner
- **Paginated article listing** (`/articles/`) — 2-column card grid, 12 per page, Previous/Next pagination with page indicator
- **Newsletter signup component** — Mailchimp-integrated form with subtle green card styling
- **Policy pages** (`/code-of-conduct`, `/policy/terms`, etc.) via dynamic catch-all route
- **Shared nav items** extracted to `src/lib/nav-items.ts` for consistency
- **SEO enhancements** on the showcase layout — OG tags, Twitter cards, article metadata, head slot for JSON-LD
- **Massive markdown cleanup** across all 188 English articles
### The Journey
Started by committing Phase 0 (markdown pipeline and typography) from a previous session, then dove straight into Phase 1. The content collections setup was smooth — Astro 5's `glob` loader with `src/content.config.ts` made it straightforward to define schemas for articles and pages.
The article template went through several iterations based on user feedback:
- Navbar needed the same variant as the homepage (extracted `navItems` to a shared file)
- Background colour corrected to the site's cream (`bg-accent-lightest-2`)
- Author/date layout went through 4 revisions before settling on: breadcrumbs → title (wide) → teaser → date/origin → rule → authors
- Newsletter signup tried 3 visual approaches: full-width dark teal (too heavy), bordered card (colour off), settled on borderless `bg-accent-lighter` card
The markdown cleanup was the biggest piece. DatoCMS exports had left behind extensive artefacts:
- 290 bold-wrapped headings (`## **Title**`)
- 686 `<u>` tags
- Non-breaking spaces (U+00A0) inside bold/italic markers — the sneakiest issue
- Character-level corruption in the Abhishek Gupta memorial article (every character wrapped in `*`)
- Ordered lists with excess indentation rendering as code blocks
- People's quotes as italic text instead of blockquotes
Used parallel subagents extensively for image analysis (scanning all 188 articles for code screenshots — found 2) and alt text generation (added descriptive alt text to 71 images across 40 articles).
### Technical Achievements
- 219 pages built from a single Astro codebase (up from ~5)
- Parallel subagent processing for image analysis across 188 articles
- Content collection schema handles complex nested structures (authors with social media, translators)
- Pagination using Astro's built-in `paginate()` without conflicting with the article `[...slug]` route (article slugs always have a language prefix like `en/`)
- Three rounds of progressively deeper markdown cleanup: automated batch fixes → formatting/quote fixes → per-article quality review
### Key Learnings
- Astro 5 uses `src/content.config.ts` (not `src/content/config.ts`) — important distinction
- `src/data/` was gitignored (for Notion-fetched data) — had to use `src/lib/` for shared code
- Non-breaking spaces (U+00A0) are invisible in editors but break markdown bold/italic rendering — they were the most pervasive issue
- DatoCMS exports can produce severely corrupted markdown (character-level `*` wrapping)
- User preference: never commit without explicit permission (added to CLAUDE.md)
### Statistics
- **10 commits** in this session
- **517 files changed**, 14,198 insertions, 128 deletions
- **195 articles** migrated (188 EN, 4 JA, 2 ZH, 1 PT)
- **4 policy pages** migrated
- **71 images** given alt text
- **2 code screenshots** replaced with markdown code blocks
- **~170 article files** touched by markdown cleanup
- **219 pages** in the built site
### Next Steps
- Multilingual support (deferred — only 7 non-English articles, will be part of a larger i18n effort)
- Fix editorial issues: wrong link targets in Thoughtworks SC article, placeholder tag in Texas State article
- Manifesto pages migration (Phase 3 in feature spec)
- Article carousel on homepage could pull from content collection instead of hardcoded data
### Reflections
The markdown cleanup was far more involved than expected. What started as "fix some bold headings and remove `<u>` tags" became a multi-pass deep dive into DatoCMS export artefacts, non-breaking space corruption, and per-article quality review. The parallel subagent approach was essential — scanning 188 articles for image issues would have been impractical in a single context window. The user's iterative feedback on the article template design was valuable — each round improved the layout significantly.
## 2026-03-08 - SCI for Web Refinements & SEE Standards Page
**Objective**: Refine the SCI for Web page based on user feedback (content tone, section ordering, article integration, link targets), then build a new SEE (Software Energy Efficiency) standards page from the draft specification.
### What We Built
- **SCI for Web page (`/standards/sci-web/`)** — extensive content revisions across ~15 sections
- Rewrote "Why the Web Needs SCI for Web" section with inline HTML to support hyperlinks to external standards (SCI, Sustainable Web Design Model, CO2.js, W3C Web Sustainability Guidelines)
- Reverted over-corrected raw report language back to paraphrased marketing-appropriate versions across TabbedSection tabs, CTACard, Third-Party Transparency, and Principles Not Prescriptions
- Reordered sections: moved SCI Formula directly after "Why the Web Needs" section, followed by Measurement Gap, then "How SCI for Web Solves This"
- Fixed heading weights — changed all TextWithImage sections to `headingStyle="light"`, changed Measurement Gap from `variant="cards"` to `variant="bordered"` for consistent heading weight
- Switched ArticleCarousel to dynamic tag-based loading from the content collection (filtered by `tags: ["sci-web"]`)
- Removed asterisk accent syntax from carousel heading (React component renders raw asterisks)
- Replaced all PDF links with article links (`/articles/en/sci-web-assembly-report/`)
- Added W3C partnership section
- **Assembly Report article** — created `/src/content/articles/en/sci-web-assembly-report/index.md`
- Full consensus design document as an article (scope definition, target personas, implementation practices)
- Tagged `["sci-web"]` for automatic carousel inclusion
- Eliminated PDF dependency — user said "I don't like PDFs at all"
- **Article tagging** — added `tags: ["sci-web"]` to three existing articles:
- `designing-sci-web-what-we-agreed-and-what-comes-next`
- `the-green-software-foundation-and-world-wide-web-consortium-w3c-collaborate-to-ad` (added to existing `["policy"]` tags)
- `pioneering-digital-sustainability-in-higher-education-meet-emma-horrell-of-the-un` (added to existing `["education"]` tags)
- **SEE page (`/standards/see/`)** — new standards page for Software Energy Efficiency
- Researched Notion for project data (lifecycle "Proposal", leads Henry Richardson + Navveen Balani)
- Content paraphrased from draft specification (user emphasised: "don't take too much from it, the spec can change significantly")
- Sections: Hero → LogoMarquee → CTACard (What is SEE?) → Why Energy Efficiency Matters → SEE Formula → Software Boundary (FeatureGrid bordered) → Sustainability Actions → Quantification Methods (FeatureGrid cards) → How SEE Relates to SCI → Mission quote → TeamGrid → Get Involved → CTABanner → Footer
- Final revision based on user feedback: rewrote "Why Energy Efficiency Matters" to focus on isolating the energy signal without grid carbon intensity variability; removed "Five Steps" and "Core Characteristics" sections; reordered Sustainability Actions before Quantification Methods; changed Quantification Methods to `variant="cards"` for visual differentiation
### The Journey
The session began as a refinement pass on SCI for Web. The user's feedback was detailed and specific — section ordering, heading boldness, content tone. A key tension emerged: the user wanted content sourced from canonical documents (the assembly report and announcement article) but paraphrased for a marketing context, not raw report language. An initial pass over-corrected by pulling verbatim report text, which the user caught and asked to revert.
A significant pivot was creating the assembly report as an article rather than linking to a PDF. This cascaded into implementing tag-based article filtering for the carousel — a pattern that makes the carousel self-maintaining as new articles are tagged.
The SEE page was the second major deliverable. The user provided the full draft spec text but was clear about keeping distance from it ("the spec can change significantly"). The page was built by paraphrasing the spec's concepts — the SEE formula, software boundary, quantification methods — into marketing-appropriate language. The final feedback round refined the "Why Energy Efficiency Matters" framing to emphasise isolating the energy signal from grid carbon intensity variability, and streamlined the page by removing less important sections.
### Technical Achievements
- **Tag-based article carousel** — dynamic content filtering from the Astro content collection, with `mainImage?.src` for proper image loading
- **Inline HTML sections** — workaround for TextBlock's plain-text `body` prop when hyperlinks are needed
- **Visual differentiation** — using FeatureGrid `variant="bordered"` vs `variant="cards"` to distinguish adjacent grid sections
### Key Learnings
- TextBlock renders `body` as plain text — use custom HTML sections when inline links are needed
- ArticleCarousel (React) doesn't support `*accent*` syntax — renders raw asterisks
- Content collection `mainImage` uses Astro's `img()` helper — returns an object with `.src` property, not a string
- When paraphrasing technical specs for marketing pages, keep distance from the source language — it's a landing page, not documentation
### Statistics
- 7 files changed across the session
- 1 new page created (`/standards/see/`)
- 1 new article created (SCI for Web Assembly Report)
- 3 articles tagged for carousel integration
### Next Steps
- Carbon Aware SDK and Impact Framework project pages
- Google Analytics integration
- Roll out to greensoftware.org (DNS + Netlify)
### Reflections
The two-page session showed the value of established patterns. The SEE page was built quickly because the SCI page template was already proven. The user's feedback on both pages was converging on the same principles: lighter heading weights, paraphrased content, signal over noise. Each standards page is getting tighter.
## 2026-03-08 - Notion Fetch Pipeline Overhaul
**Objective**: Optimise the Notion data fetch pipeline — reduce query count, eliminate the `volunteerBySubId` cross-reference, fix `logos.json` dependencies, restore team photos.
### What We Built
- **`extractPersonFromSub(sub, memberById)`** — new function reading person data directly from Subscription DB roll-up fields (First Name, Surname, Title, LinkedIn, Member, Volunteer Status). Eliminates the need to look up each subscription against the Volunteers DB.
- **Refactored three people-fetching functions** — `fetchSteeringCommittee`, `fetchChairsAndLeads`, `fetchOrgLeads` now all call `extractPersonFromSub` instead of `extractPerson`. No `volunteerBySubId` map needed.
- **Volunteer status warnings** — people with active subscriptions but non-Active volunteer status (Nicole McCaffrey "Invalid", Richard Hill "New") are included with a `WARN` log rather than silently dropped.
- **Eliminated `logos.json`** — the derived file is gone. `LogoMarquee` and all other consumers now read directly from `members.json`, filtering by `active && logo && !hideLogo` at runtime.
- **Photo fallback via `volunteerPhotoByName`** — Notion roll-up file properties don't return signed URLs (a platform limitation). Fixed by building a `volunteerPhotoByName: Map<fullName.toLowerCase(), { url, origName }>` from `allActiveVolunteers` in `main()` and using it as a fallback in `extractPersonFromSub`.
- **Removed `countAll` call** — replaced `countAll(DS.VOLUNTEERS)` with `allActiveVolunteers.length` since we already fetch all active volunteers.
### The Journey
The session picked up mid-refactor from the previous conversation. The goal was clear: the subscription roll-up fields (First Name, Surname, Title, LinkedIn, Photo, Member, Volunteer Status) had been added to Notion by the user specifically to avoid the expensive volunteer pre-fetch-and-cross-reference pattern.
The first implementation went smoothly — `extractPersonFromSub` written, three functions updated, `volunteerBySubId` building removed from `main()`. A full fetch confirmed 66 org leads (up from 65 — the two previously-missing people now included with warnings). Build passed cleanly.
Then came the `logos.json` removal. The user rightly pointed out the file was still being generated after the marquee had already been migrated. Removing the generation from the fetch script immediately surfaced that six pages (`index.astro`, four story pages, `sci-web/index.astro`, and `_catalogue/logo.astro`) were still importing `logos.json` directly. All were updated to import `members.json` and rebuild the logo mapping inline.
The photo bug was more subtle. After re-fetching with fresh data, team photos were showing for 12 admin team members but nobody else — steering committee, chairs, org leads all blank. The culprit: Notion's API does not return signed file download URLs through roll-up properties. The `Photo` roll-up always comes back with an empty `files` array. The fix was to build `volunteerPhotoByName` from the `allActiveVolunteers` records (which do have direct file properties with real URLs) and use it as a fallback in `extractPersonFromSub`. This brought photos up from 12 to 51 people. The remaining 53 simply don't have photos set in Notion.
### Technical Achievements
- **29→37s fetch time** (with photos restored — still well under the ~70s baseline from the start of the optimisation arc)
- **`logos.json` fully eliminated** — 6 consumer files updated, one derived file removed from generation
- **Org leads: 65→66** — two people with non-Active volunteer status now included
- **Team photos: 12→51** — photo fallback via volunteer records, working around Notion's roll-up file URL limitation
- **`volunteerBySubId` map eliminated** — no more 726-volunteer cross-reference for subscription-based lookups
### Key Learnings
- **Notion roll-ups don't return signed file URLs** — this is a hard platform limitation. Files properties in roll-ups always return an empty `files` array (or file names without URLs). Always read photos from the source record, not a roll-up.
- **`downloadFile` skip logic hides stale data** — because it skips downloads when files exist on disk, a re-run after a code change doesn't re-download unless `--force` is passed. Useful for speed; can mask bugs.
- **`logos.json` as a derived file was fragile** — having a separate generated file that consumers import means two things to keep in sync. Reading `members.json` directly is simpler and always fresh.
### Statistics
- 9 files changed: `fetch-notion-data.cjs`, `logo-marquee.astro`, `_catalogue/logo.astro`, `index.astro`, `sci-web/index.astro`, 4 story pages
- 186 insertions, 135 deletions in the fetch script
- Photos restored: 12 → 51 people with photos
- Fetch time: ~29–37s (from ~70s before the optimisation arc)
### Next Steps
- Fix Nicole McCaffrey and Richard Hill Volunteer Status in Notion to remove the warnings
- Carbon Aware SDK and Impact Framework project pages
- Google Analytics integration
- Roll out to greensoftware.org
### Reflections
This session was a satisfying data-plumbing session. The Notion roll-up photo limitation was an excellent example of why you need to test with real data, not just check that the code looks right — the API response structure was correct but deliberately incomplete. The `logos.json` cleanup was a good reminder that derived files add maintenance surface: when the source changes, the derived file silently becomes stale.