Skip to content

Commit 975b3c5

Browse files
authored
Merge branch 'sugarlabs:master' into master
2 parents d2a087c + da12f94 commit 975b3c5

File tree

7 files changed

+192
-113
lines changed

7 files changed

+192
-113
lines changed
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
name: Lighthouse CI
2+
3+
on:
4+
pull_request_target:
5+
types: [opened, synchronize, reopened]
6+
push:
7+
branches: [master]
8+
9+
jobs:
10+
lighthouse:
11+
name: Lighthouse Performance Audit
12+
runs-on: ubuntu-latest
13+
permissions:
14+
pull-requests: write
15+
contents: read
16+
issues: write
17+
18+
steps:
19+
- name: Checkout code
20+
uses: actions/checkout@v4
21+
with:
22+
fetch-depth: 0
23+
ref: ${{ github.event.pull_request.head.sha || github.sha }}
24+
25+
- name: Setup Node.js
26+
uses: actions/setup-node@v4
27+
with:
28+
node-version: '20'
29+
cache: 'npm'
30+
31+
- name: Install dependencies
32+
run: npm ci
33+
34+
- name: Install Lighthouse CI
35+
run: npm install -g @lhci/cli@0.14.x
36+
37+
- name: Run Lighthouse CI
38+
id: lighthouse
39+
run: |
40+
lhci autorun --config=./lighthouserc.js 2>&1 | tee lighthouse-output.txt
41+
echo "LIGHTHOUSE_EXIT_CODE=$?" >> $GITHUB_ENV
42+
43+
- name: Parse Lighthouse Results
44+
id: parse
45+
run: |
46+
# Extract scores from the lighthouse output
47+
# Create a results summary
48+
mkdir -p lighthouse-results
49+
50+
# Find the latest report
51+
REPORT_DIR=".lighthouseci"
52+
if [ -d "$REPORT_DIR" ]; then
53+
# Get the manifest file
54+
MANIFEST="$REPORT_DIR/manifest.json"
55+
if [ -f "$MANIFEST" ]; then
56+
# Extract average scores from manifest
57+
PERF_SCORE=$(jq '[.[].summary.performance] | add / length * 100 | floor' "$MANIFEST" 2>/dev/null || echo "N/A")
58+
A11Y_SCORE=$(jq '[.[].summary.accessibility] | add / length * 100 | floor' "$MANIFEST" 2>/dev/null || echo "N/A")
59+
BP_SCORE=$(jq '[.[].summary["best-practices"]] | add / length * 100 | floor' "$MANIFEST" 2>/dev/null || echo "N/A")
60+
SEO_SCORE=$(jq '[.[].summary.seo] | add / length * 100 | floor' "$MANIFEST" 2>/dev/null || echo "N/A")
61+
62+
# Get report URL from temporary-public-storage
63+
REPORT_URL=$(jq -r '.[0].url // "N/A"' "$MANIFEST" 2>/dev/null)
64+
65+
echo "PERF_SCORE=$PERF_SCORE" >> $GITHUB_OUTPUT
66+
echo "A11Y_SCORE=$A11Y_SCORE" >> $GITHUB_OUTPUT
67+
echo "BP_SCORE=$BP_SCORE" >> $GITHUB_OUTPUT
68+
echo "SEO_SCORE=$SEO_SCORE" >> $GITHUB_OUTPUT
69+
echo "REPORT_URL=$REPORT_URL" >> $GITHUB_OUTPUT
70+
fi
71+
fi
72+
73+
- name: Get Score Emoji
74+
id: emoji
75+
run: |
76+
get_emoji() {
77+
score=$1
78+
if [ "$score" = "N/A" ]; then
79+
echo "⚪"
80+
elif [ "$score" -ge 90 ]; then
81+
echo "🟢"
82+
elif [ "$score" -ge 50 ]; then
83+
echo "🟠"
84+
else
85+
echo "🔴"
86+
fi
87+
}
88+
89+
echo "PERF_EMOJI=$(get_emoji '${{ steps.parse.outputs.PERF_SCORE }}')" >> $GITHUB_OUTPUT
90+
echo "A11Y_EMOJI=$(get_emoji '${{ steps.parse.outputs.A11Y_SCORE }}')" >> $GITHUB_OUTPUT
91+
echo "BP_EMOJI=$(get_emoji '${{ steps.parse.outputs.BP_SCORE }}')" >> $GITHUB_OUTPUT
92+
echo "SEO_EMOJI=$(get_emoji '${{ steps.parse.outputs.SEO_SCORE }}')" >> $GITHUB_OUTPUT
93+
94+
- name: Comment on PR
95+
if: github.event_name == 'pull_request'
96+
uses: thollander/actions-comment-pull-request@v3
97+
with:
98+
message: |
99+
## 🔦 Lighthouse Performance Report
100+
101+
| Category | Score | Status |
102+
|----------|-------|--------|
103+
| ⚡ Performance | **${{ steps.parse.outputs.PERF_SCORE }}** | ${{ steps.emoji.outputs.PERF_EMOJI }} |
104+
| ♿ Accessibility | **${{ steps.parse.outputs.A11Y_SCORE }}** | ${{ steps.emoji.outputs.A11Y_EMOJI }} |
105+
| ✅ Best Practices | **${{ steps.parse.outputs.BP_SCORE }}** | ${{ steps.emoji.outputs.BP_EMOJI }} |
106+
| 🔍 SEO | **${{ steps.parse.outputs.SEO_SCORE }}** | ${{ steps.emoji.outputs.SEO_EMOJI }} |
107+
108+
### Score Legend
109+
- 🟢 90-100: Excellent
110+
- 🟠 50-89: Needs Improvement
111+
- 🔴 0-49: Poor
112+
113+
> 📊 [View Full Lighthouse Report](${{ steps.parse.outputs.REPORT_URL }})
114+
115+
---
116+
*This performance audit helps ensure PRs don't negatively impact user experience.*
117+
comment-tag: lighthouse-report
118+
mode: recreate
119+
pr-number: ${{ github.event.pull_request.number }}
120+
121+
- name: Upload Lighthouse Reports
122+
uses: actions/upload-artifact@v4
123+
if: always()
124+
with:
125+
name: lighthouse-reports
126+
path: .lighthouseci/
127+
retention-days: 30

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,7 @@ node_modules/
22
*~
33
coverage/
44
.DS_Store
5-
**/.DS_Store
5+
**/.DS_Store
6+
7+
# Lighthouse CI
8+
.lighthouseci/

js/block.js

Lines changed: 0 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -4182,115 +4182,6 @@ class Block {
41824182
return new Date().getTime() - this._piemenuExitTime > 200;
41834183
}
41844184

4185-
/**
4186-
* Checks if the block's input is a number value.
4187-
* @private
4188-
* @param {number} c - The index of the connection.
4189-
* @returns {boolean} - True if the input is a number value, false otherwise.
4190-
*/
4191-
_noteValueNumber(c) {
4192-
// Is this a number block being used as a note value
4193-
// denominator argument?
4194-
const dblk = this.connections[0];
4195-
// Are we connected to a divide block?
4196-
if (
4197-
this.name === "number" &&
4198-
dblk !== null &&
4199-
this.blocks.blockList[dblk].name === "divide"
4200-
) {
4201-
// Are we the denominator (c == 2) or numerator (c == 1)?
4202-
if (
4203-
this.blocks.blockList[dblk].connections[c] === this.blocks.blockList.indexOf(this)
4204-
) {
4205-
// Is the divide block connected to a note value block?
4206-
const cblk = this.blocks.blockList[dblk].connections[0];
4207-
if (cblk !== null) {
4208-
// Is it the first or second arg?
4209-
switch (this.blocks.blockList[cblk].name) {
4210-
case "newnote":
4211-
case "pickup":
4212-
case "tuplet4":
4213-
case "newstaccato":
4214-
case "newslur":
4215-
case "elapsednotes2":
4216-
return this.blocks.blockList[cblk].connections[1] === dblk;
4217-
case "meter":
4218-
this._check_meter_block = cblk;
4219-
// eslint-disable-next-line no-fallthrough
4220-
case "setbpm2":
4221-
case "setmasterbpm2":
4222-
case "stuplet":
4223-
case "rhythm2":
4224-
case "newswing2":
4225-
case "vibrato":
4226-
case "neighbor":
4227-
case "neighbor2":
4228-
return this.blocks.blockList[cblk].connections[2] === dblk;
4229-
default:
4230-
return false;
4231-
}
4232-
}
4233-
}
4234-
}
4235-
4236-
return false;
4237-
}
4238-
4239-
/**
4240-
* Gets the value of the number block being used as a note value.
4241-
* @private
4242-
* @returns {number} - The value of the number block being used as a note value.
4243-
*/
4244-
_noteValueValue() {
4245-
// Return the number block value being used as a note value
4246-
// denominator argument.
4247-
const dblk = this.connections[0];
4248-
// We are connected to a divide block.
4249-
// Is the divide block connected to a note value block?
4250-
let cblk = this.blocks.blockList[dblk].connections[0];
4251-
if (cblk !== null) {
4252-
// Is it the first or second arg?
4253-
switch (this.blocks.blockList[cblk].name) {
4254-
case "newnote":
4255-
case "pickup":
4256-
case "tuplet4":
4257-
case "newstaccato":
4258-
case "newslur":
4259-
case "elapsednotes2":
4260-
if (this.blocks.blockList[cblk].connections[1] === dblk) {
4261-
cblk = this.blocks.blockList[dblk].connections[2];
4262-
return this.blocks.blockList[cblk].value;
4263-
} else {
4264-
return 1;
4265-
}
4266-
case "meter":
4267-
this._check_meter_block = cblk;
4268-
// eslint-disable-next-line no-fallthrough
4269-
case "setbpm2":
4270-
case "setmasterbpm2":
4271-
case "stuplet":
4272-
case "rhythm2":
4273-
case "newswing2":
4274-
case "vibrato":
4275-
case "neighbor":
4276-
case "neighbor2":
4277-
if (this.blocks.blockList[cblk].connections[2] === dblk) {
4278-
if (this.blocks.blockList[cblk].connections[1] === dblk) {
4279-
cblk = this.blocks.blockList[dblk].connections[2];
4280-
return this.blocks.blockList[cblk].value;
4281-
} else {
4282-
return 1;
4283-
}
4284-
} else {
4285-
return 1;
4286-
}
4287-
default:
4288-
return 1;
4289-
}
4290-
}
4291-
4292-
return 1;
4293-
}
42944185

42954186
/**
42964187
* Checks and reinitializes widget windows if their labels are changed.

js/languagebox.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,11 @@ class LanguageBox {
234234
this._language = this._language.split("-")[0];
235235
}
236236

237-
localStorage.setItem("languagePreference", this._language);
237+
try {
238+
localStorage.setItem("languagePreference", this._language);
239+
} catch (e) {
240+
console.warn("Could not save language preference:", e);
241+
}
238242
this.activity.textMsg(_("Music Blocks is already set to this language."));
239243
} else {
240244
this.activity.storage.languagePreference = this._language;

js/themebox.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,11 @@ class ThemeBox {
263263
} else {
264264
// Save preference to localStorage
265265
this.activity.storage.themePreference = this._theme;
266-
localStorage.setItem("themePreference", this._theme);
266+
try {
267+
localStorage.setItem("themePreference", this._theme);
268+
} catch (e) {
269+
console.warn("Could not save theme preference:", e);
270+
}
267271

268272
// Apply theme instantly instead of reloading
269273
this.applyThemeInstantly();

js/widgets/reflection.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -575,7 +575,11 @@ class ReflectionMatrix {
575575
*/
576576
saveReport(data) {
577577
const key = "musicblocks_analysis";
578-
localStorage.setItem(key, data.response);
578+
try {
579+
localStorage.setItem(key, data.response);
580+
} catch (e) {
581+
console.warn("Could not save analysis report to localStorage:", e);
582+
}
579583
console.log("Conversation saved in localStorage.");
580584
}
581585

lighthouserc.js

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
module.exports = {
2+
ci: {
3+
collect: {
4+
// Use the static server to serve Music Blocks
5+
staticDistDir: "./",
6+
// Number of runs to average for more accurate results
7+
numberOfRuns: 3,
8+
// URLs to test
9+
url: ["http://localhost/index.html"],
10+
settings: {
11+
// Chrome flags for headless testing
12+
chromeFlags: "--headless --no-sandbox --disable-gpu",
13+
// Categories to audit
14+
onlyCategories: ["performance", "accessibility", "best-practices", "seo"],
15+
// Emulate mobile device for realistic testing
16+
preset: "desktop",
17+
// Set throttling for consistent results
18+
throttling: {
19+
rttMs: 40,
20+
throughputKbps: 10240,
21+
cpuSlowdownMultiplier: 1
22+
}
23+
}
24+
},
25+
assert: {
26+
// Assertions for performance budgets
27+
// These will warn but not fail the build initially
28+
assertions: {
29+
"categories:performance": ["warn", { minScore: 0.5 }],
30+
"categories:accessibility": ["warn", { minScore: 0.8 }],
31+
"categories:best-practices": ["warn", { minScore: 0.8 }],
32+
"categories:seo": ["warn", { minScore: 0.8 }],
33+
// Specific metrics to track
34+
"first-contentful-paint": ["warn", { maxNumericValue: 4000 }],
35+
"largest-contentful-paint": ["warn", { maxNumericValue: 6000 }],
36+
"cumulative-layout-shift": ["warn", { maxNumericValue: 0.25 }],
37+
"total-blocking-time": ["warn", { maxNumericValue: 600 }],
38+
"speed-index": ["warn", { maxNumericValue: 5000 }]
39+
}
40+
},
41+
upload: {
42+
// Use temporary public storage for reports (free, no setup required)
43+
target: "temporary-public-storage"
44+
}
45+
}
46+
};

0 commit comments

Comments
 (0)