Skip to content

Commit b15fe84

Browse files
authored
Merge branch 'main' into copilot/simplify-c-code-java
2 parents 37c1bc9 + 4ba22c3 commit b15fe84

File tree

18 files changed

+128
-177
lines changed

18 files changed

+128
-177
lines changed

.github/copilot-instructions.md

Lines changed: 83 additions & 169 deletions
Original file line numberDiff line numberDiff line change
@@ -1,192 +1,106 @@
11
# Copilot Instructions for java.evolved
22

3-
## Project Overview
3+
## Build & Serve
44

5-
This is a static site showcasing modern Java patterns vs legacy approaches.
6-
It is hosted on GitHub Pages at https://javaevolved.github.io.
5+
```bash
6+
jbang html-generators/generate.java # Rebuild all locales
7+
jbang html-generators/generate.java --locale es # Rebuild single locale
8+
jwebserver -b 0.0.0.0 -d site -p 8090 # Serve locally
9+
```
10+
11+
Requires **Java 25+** and **JBang**. No npm, Maven, or Gradle.
12+
13+
**Validation:** There is no test suite. Validate changes by running the generator and confirming it completes without errors. The `proof/` directory contains one JBang script per pattern that proves the modern code compiles:
14+
15+
```bash
16+
jbang proof/language/TypeInferenceWithVar.java # Run single proof
17+
```
718

819
## Architecture
920

10-
### Source of Truth: JSON Files
21+
Static site generator: YAML content → JBang generator → HTML pages.
1122

12-
Each pattern is defined as a JSON file under **`content/category/`**:
23+
- **`content/`** — Source of truth. One YAML/JSON file per pattern, organized by category.
24+
- **`templates/`** — HTML templates with `{{placeholder}}` tokens. The generator replaces tokens with content fields and UI strings.
25+
- **`html-generators/generate.java`** — JBang script that reads content + translations + templates and produces all HTML under `site/`.
26+
- **`site/app.js`** and **`site/styles.css`** — Manually maintained client-side code (vanilla JS, no frameworks).
27+
- **`translations/strings/{locale}.yaml`** — UI string translations (labels, nav, footer). Tokens use dotted keys: `{{nav.allPatterns}}`.
28+
- **`translations/content/{locale}/`** — Partial content translations (only translatable fields; structural data always comes from English source).
29+
- **`proof/{category}/{PascalCaseSlug}.java`** — JBang scripts proving each pattern's modern code compiles on Java 25.
1330

14-
```
15-
content/language/type-inference-with-var.json
16-
content/collections/immutable-list-creation.json
17-
content/streams/stream-tolist.json
18-
...
19-
```
31+
### Generated files — DO NOT EDIT directly
2032

21-
**Categories:** `language`, `collections`, `strings`, `streams`, `concurrency`, `io`, `errors`, `datetime`, `security`, `tooling`, `enterprise`
33+
Everything under `site/` except `app.js` and `styles.css` is generated:
34+
- `site/index.html`, `site/{category}/*.html`, `site/data/snippets.json`
35+
- `site/{locale}/index.html`, `site/{locale}/{category}/*.html`, `site/{locale}/data/snippets.json`
2236

23-
### Generated Files (DO NOT EDIT)
37+
Run the generator to rebuild after any content, template, or translation change.
2438

25-
The following are **generated by `html-generators/generate.java`** and must not be edited directly:
39+
## Content Schema
2640

27-
- `site/index.html` — English homepage with preview cards (generated from `templates/index.html`)
28-
- `site/language/*.html`, `site/collections/*.html`, etc. — English detail pages
29-
- `site/data/snippets.json` — English aggregated search index
30-
- `site/{locale}/index.html` — localized homepage (e.g., `site/es/index.html`)
31-
- `site/{locale}/language/*.html`, etc. — localized detail pages
32-
- `site/{locale}/data/snippets.json` — localized search index
41+
Content files are YAML (preferred) or JSON under `content/{category}/{slug}.yaml`. The generator auto-detects format by extension (`.yaml`, `.yml`, `.json`).
3342

34-
Run `jbang html-generators/generate.java` to rebuild all generated files from the JSON sources and translations.
43+
### Required fields
3544

36-
### Manually Maintained Files
45+
| Field | Constraint |
46+
|-------|-----------|
47+
| `slug` | Must match filename (without extension) |
48+
| `category` | Must match parent folder name |
49+
| `whyModernWins` | Exactly **3** entries, each with `icon`, `title`, `desc` |
50+
| `related` | Exactly **3** entries as `category/slug` paths (cross-category OK) |
51+
| `docs` | At least **1** entry with `title` and `href` |
52+
| `prev` / `next` | `category/slug` path or `null` for first/last in the global chain |
53+
| `jdkVersion` | The JDK version where the feature became **final** (not preview) |
54+
| `difficulty` | One of: `beginner`, `intermediate`, `advanced` |
55+
| `support.state` | One of: `available`, `preview`, `experimental` |
3756

38-
- `site/app.js` — client-side search, filtering, code highlighting, locale detection
39-
- `site/styles.css` — all styling
40-
- `templates/slug-template.html` — HTML template with `{{placeholder}}` tokens (content + UI strings) used by the generator
41-
- `templates/index.html` — homepage template with `{{tipCards}}`, `{{snippetCount}}`, and UI string placeholders
42-
- `templates/index-card.html` — preview card template for the homepage grid
43-
- `html-generators/categories.properties` — category ID → display name mapping
44-
- `html-generators/locales.properties` — supported locales registry (locale=Display name)
45-
- `translations/strings/{locale}.yaml` — UI strings per locale (labels, nav, footer, etc.)
46-
- `translations/content/{locale}/` — translated pattern JSON files (partial, translatable fields only)
57+
### Adding a new pattern
4758

48-
### Project Structure
59+
1. Create `content/{category}/new-slug.yaml` with all required fields (use `content/template.json` as reference).
60+
2. Update `prev`/`next` in the adjacent patterns to maintain the navigation chain.
61+
3. Create `proof/{category}/{PascalCaseSlug}.java` — JBang script wrapping the modern code.
62+
4. Run `jbang html-generators/generate.java` and verify it completes.
63+
5. Translations are optional — the AI translation workflow handles them, or create partial files under `translations/content/{locale}/`.
4964

50-
```
51-
content/ # English content JSON files (source of truth, one per pattern)
52-
translations/ # All i18n artifacts
53-
strings/ # UI strings per locale (en.yaml, es.yaml, pt-BR.yaml)
54-
content/ # Translated pattern files per locale (partial, translatable fields only)
55-
es/ # Spanish translations (mirrors content/ folder structure)
56-
pt-BR/ # Brazilian Portuguese translations
57-
site/ # Deployable site (static assets + generated HTML)
58-
es/ # Generated Spanish pages
59-
pt-BR/ # Generated Portuguese pages
60-
templates/ # HTML templates with {{…}} tokens for content + UI strings
61-
html-generators/ # Build scripts, categories.properties, locales.properties
62-
specs/ # Feature specifications (e.g., i18n-spec.md)
63-
```
65+
### Removing or reordering a pattern
6466

65-
## JSON Snippet Schema
66-
67-
Each `content/category/slug.json` file has this structure:
68-
69-
```json
70-
{
71-
"id": 1,
72-
"slug": "type-inference-with-var",
73-
"title": "Type inference with var",
74-
"category": "language",
75-
"difficulty": "beginner|intermediate|advanced",
76-
"jdkVersion": "10",
77-
"oldLabel": "Java 8",
78-
"modernLabel": "Java 10+",
79-
"oldApproach": "Explicit Types",
80-
"modernApproach": "var keyword",
81-
"oldCode": "// old way...",
82-
"modernCode": "// modern way...",
83-
"summary": "One-line description.",
84-
"explanation": "How it works paragraph.",
85-
"whyModernWins": [
86-
{ "icon": "", "title": "Short title", "desc": "One sentence." },
87-
{ "icon": "👁", "title": "Short title", "desc": "One sentence." },
88-
{ "icon": "🔒", "title": "Short title", "desc": "One sentence." }
89-
],
90-
"support": {
91-
"state": "available",
92-
"description": "Widely available since JDK 10 (March 2018)"
93-
},
94-
"prev": "category/slug-of-previous",
95-
"next": "category/slug-of-next",
96-
"related": [
97-
"category/slug-1",
98-
"category/slug-2",
99-
"category/slug-3"
100-
],
101-
"docs": [
102-
{ "title": "Javadoc or Guide Title", "href": "https://docs.oracle.com/..." }
103-
]
104-
}
105-
```
67+
Update `prev`/`next` in adjacent patterns. Search for the slug in other patterns' `related` arrays and replace with an appropriate alternative.
68+
69+
## Internationalization
70+
71+
Full spec: `specs/i18n/i18n-spec.md`. Key rules:
72+
73+
- All locales (including English) go through the same build pipeline.
74+
- UI strings: `translations/strings/{locale}.yaml`. Missing keys fall back to English with a build-time warning.
75+
- Content translations contain **only** translatable fields: `title`, `summary`, `explanation`, `oldApproach`, `modernApproach`, `whyModernWins`, `support.description`. Code, slugs, navigation, and docs are never translated.
76+
- `oldCode`/`modernCode` in translation files are **always overwritten** with English values at build time to prevent hallucinated code.
77+
- Locale registry: `html-generators/locales.properties` (format: `locale=Display Name`).
78+
- When adding a new UI string key, add it to `en.yaml` first, then to all other locale files. The generator warns on missing keys but doesn't fail.
79+
- YAML colons in string values must be quoted (Jackson parser is stricter than PyYAML).
80+
81+
## Proof Files
10682

107-
### Key Rules
108-
109-
- `slug` must match the filename (without `.json`)
110-
- `category` must match the parent folder name
111-
- `whyModernWins` must have exactly **3** entries
112-
- `related` must have exactly **3** entries (as `category/slug` paths)
113-
- `docs` must have at least **1** entry linking to Javadoc or Oracle documentation
114-
- `prev`/`next` are `category/slug` paths or `null` for first/last
115-
- Code in `oldCode`/`modernCode` uses `\n` for newlines
116-
117-
## Category Display Names
118-
119-
Categories and their display names are defined in `html-generators/categories.properties`:
120-
121-
| ID | Display |
122-
|----|---------|
123-
| `language` | Language |
124-
| `collections` | Collections |
125-
| `strings` | Strings |
126-
| `streams` | Streams |
127-
| `concurrency` | Concurrency |
128-
| `io` | I/O |
129-
| `errors` | Errors |
130-
| `datetime` | Date/Time |
131-
| `security` | Security |
132-
| `tooling` | Tooling |
133-
| `enterprise` | Enterprise |
134-
135-
## Adding a New Pattern
136-
137-
1. Create `content/category/new-slug.json` with all required fields
138-
2. Update `prev`/`next` in the adjacent patterns' JSON files
139-
3. Run `jbang html-generators/generate.java`
140-
4. (Optional) Create translated content files under `translations/content/{locale}/category/new-slug.json` with only translatable fields — or let the AI translation workflow handle it
141-
142-
## Internationalization (i18n)
143-
144-
The site supports multiple languages. See `specs/i18n/i18n-spec.md` for the full specification.
145-
146-
### Key Concepts
147-
148-
- **UI strings:** Hard-coded template text (labels, nav, footer) is extracted into `translations/strings/{locale}.yaml`. Templates use `{{dotted.key}}` tokens (e.g., `{{nav.allPatterns}}`, `{{sections.codeComparison}}`). Missing keys fall back to the English value with a build-time warning.
149-
- **Content translations:** Translated pattern files under `translations/content/{locale}/` contain **only** translatable fields (`title`, `summary`, `explanation`, `oldApproach`, `modernApproach`, `whyModernWins`, `support.description`). All other fields (`oldCode`, `modernCode`, `slug`, `id`, `prev`, `next`, `related`, `docs`, etc.) are always taken from the English source.
150-
- **Locale registry:** `html-generators/locales.properties` lists supported locales (format: `locale=Display name`). The first entry is the default.
151-
- **English is a first-class locale:** All locales — including English — go through the same build pipeline.
152-
- **Fallback:** If a pattern has no translation file for a locale, the English content is used and an "untranslated" banner is shown.
153-
154-
### Supported Locales
155-
156-
Defined in `html-generators/locales.properties`:
157-
158-
| Locale | Display Name |
159-
|--------|-------------|
160-
| `en` | English |
161-
| `es` | Español |
162-
| `pt-BR` | Português (Brasil) |
163-
164-
### Content Translation File Example
165-
166-
Translation files contain **only** translatable fields — no structural data:
167-
168-
```json
169-
// translations/content/pt-BR/language/type-inference-with-var.json
170-
{
171-
"title": "Inferência de tipo com var",
172-
"oldApproach": "Tipos explícitos",
173-
"modernApproach": "Palavra-chave var",
174-
"summary": "Use var para deixar o compilador inferir o tipo local.",
175-
"explanation": "...",
176-
"whyModernWins": [
177-
{ "icon": "", "title": "Menos ruído", "desc": "..." },
178-
{ "icon": "👁", "title": "Mais legível", "desc": "..." },
179-
{ "icon": "🔒", "title": "Seguro", "desc": "..." }
180-
],
181-
"support": {
182-
"description": "Amplamente disponível desde o JDK 10 (março de 2018)"
183-
}
83+
Each pattern has a corresponding proof file: `proof/{category}/{PascalCaseSlug}.java`.
84+
85+
```java
86+
///usr/bin/env jbang "$0" "$@" ; exit $?
87+
//JAVA 25+
88+
89+
/// Proof: slug-name
90+
/// Source: content/category/slug-name.yaml
91+
void main() {
92+
// modern code only — no old code, no assertions
18493
}
18594
```
18695

187-
## Local Development
96+
Uses Java 25 implicit classes (`void main()`, not `static void main`). Add minimal scaffolding (imports, dummy variables) to make the modern code compile.
18897

189-
```bash
190-
jbang html-generators/generate.java # Build HTML pages + snippets.json
191-
jwebserver -d site -p 8090 # Serve locally
192-
```
98+
## Key Conventions
99+
100+
- **Vanilla JS only**`site/app.js` uses no frameworks or build tools.
101+
- **Category display names** are defined in `html-generators/categories.properties`, not hardcoded.
102+
- **JDK filter ranges** in `app.js` map LTS versions to ranges: `11→[9-11]`, `17→[12-17]`, `21→[18-21]`, `25→[22-25]`.
103+
- **JetBrains Mono ligatures** are disabled on `.code-text` elements to prevent operators like `->` from rendering as special characters.
104+
- **Dark theme** uses CSS custom properties (`--modern-bg`, `--old-bg`). Theme state is in `localStorage.theme` and `data-theme` on `<html>`.
105+
- **RTL support** — Arabic (`ar`) locale sets `dir="rtl"` on the page.
106+
- When both old and modern approaches are from the same JDK version, use descriptive labels (e.g., "Full syntax" / "Compact") instead of version numbers.

proof/language/CallCFromJava.java

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,25 @@
11
///usr/bin/env jbang "$0" "$@" ; exit $?
22
//JAVA 25+
33
//JAVA_OPTIONS --enable-native-access=ALL-UNNAMED
4+
45
import java.lang.foreign.*;
6+
import java.lang.invoke.MethodHandle;
7+
import java.util.Optional;
58

69
/// Proof: call-c-from-java
710
/// Source: content/language/call-c-from-java.yaml
811
void main() throws Throwable {
9-
try (var arena = Arena.ofConfined()) {
10-
var stdlib = Linker.nativeLinker().defaultLookup();
11-
var foreignFuncAddr = stdlib.find("strlen").orElseThrow();
12-
var strlenSig =
12+
13+
try (Arena arena = Arena.ofConfined()) {
14+
// Use a system library to prove FFM compiles and links
15+
SymbolLookup stdlib = Linker.nativeLinker().defaultLookup();
16+
Optional<MemorySegment> segment = stdlib.find("strlen");
17+
MemorySegment foreignFuncAddr = segment.get();
18+
FunctionDescriptor strlen_sig =
1319
FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS);
14-
var strlenMethod =
15-
Linker.nativeLinker().downcallHandle(foreignFuncAddr, strlenSig);
20+
MethodHandle strlenMethod =
21+
Linker.nativeLinker().downcallHandle(foreignFuncAddr, strlen_sig);
1622
var ret = (long) strlenMethod.invokeExact(arena.allocateFrom("Bambi"));
17-
assert ret == 5 : "Expected strlen(\"Bambi\") == 5, got " + ret;
23+
System.out.println("Return value " + ret);
1824
}
1925
}

site/app.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,10 @@
216216
'25': [22, 25]
217217
};
218218

219+
const noResultsMsg = document.getElementById('noResultsMessage');
220+
219221
const applyFilters = () => {
222+
let visibleCount = 0;
220223
cards.forEach(card => {
221224
const matchesCategory = !activeCategory || card.dataset.category === activeCategory;
222225
let matchesJdk = true;
@@ -225,9 +228,15 @@
225228
const range = LTS_RANGES[activeJdk];
226229
matchesJdk = range && version >= range[0] && version <= range[1];
227230
}
228-
card.classList.toggle('filter-hidden', !(matchesCategory && matchesJdk));
231+
const visible = matchesCategory && matchesJdk;
232+
card.classList.toggle('filter-hidden', !visible);
233+
if (visible) visibleCount++;
229234
});
230235

236+
if (noResultsMsg) {
237+
noResultsMsg.style.display = visibleCount === 0 ? '' : 'none';
238+
}
239+
231240
if (window.updateViewToggleState) {
232241
window.updateViewToggleState();
233242
}

site/styles.css

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,14 @@ nav {
415415
display: none;
416416
}
417417

418+
.no-results-message {
419+
text-align: center;
420+
color: var(--text-muted);
421+
padding: 3rem 1rem;
422+
font-size: 1.1rem;
423+
width: 100%;
424+
}
425+
418426
.tip-card {
419427
background: var(--surface);
420428
border: 1px solid var(--border);

templates/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,7 @@ <h2 class="section-title">{{site.allComparisons}}</h2>
275275
<div class="tips-grid" id="tipsGrid">
276276
{{tipCards}}
277277
</div>
278+
<p class="no-results-message" id="noResultsMessage" style="display:none">{{filters.noResults}}</p>
278279
</section>
279280

280281
<div class="stats-bar">

translations/strings/ar.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ sections:
3131
filters:
3232
show: "عرض:"
3333
all: الكل
34+
noResults: لم يتم العثور على أنماط برمجية لهذا الفلتر.
3435
difficulty:
3536
beginner: مبتدئ
3637
intermediate: متوسط

translations/strings/bn.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ filters:
3434
all: সব
3535
jdk: 'JDK:'
3636
category: 'বিভাগ:'
37+
noResults: এই ফিল্টারের জন্য কোনো কোড প্যাটার্ন পাওয়া যায়নি।
3738
difficulty:
3839
beginner: প্রাথমিক
3940
intermediate: মধ্যম

translations/strings/de.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ sections:
3333
filters:
3434
show: 'Anzeigen:'
3535
all: Alle
36+
noResults: Keine Code-Muster für diesen Filter gefunden.
3637
difficulty:
3738
beginner: Einsteiger
3839
intermediate: Fortgeschritten

translations/strings/en.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ filters:
3535
all: All
3636
jdk: 'JDK:'
3737
category: 'Category:'
38+
noResults: No code patterns found for this filter.
3839
difficulty:
3940
beginner: Beginner
4041
intermediate: Intermediate

translations/strings/es.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ sections:
3333
filters:
3434
show: 'Mostrar:'
3535
all: Todos
36+
noResults: No se encontraron patrones de código para este filtro.
3637
difficulty:
3738
beginner: Principiante
3839
intermediate: Intermedio

0 commit comments

Comments
 (0)