Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b8c157c3 authored by Romain Hunault's avatar Romain Hunault 💻
Browse files

Display a label instead of the score

parent df6264a0
Loading
Loading
Loading
Loading
+3 −2
Original line number Diff line number Diff line
# AGENTS Methods Log
- 2025-12-04: Added `scripts/build_site.rb` to read the JSON-based config, echo the `VERSION`, respect `LANGUAGES` overrides, emit `dynamic/selected-languages.yml`, log failures into `build_site.err.txt`, and run Jekyll through the generated config so Docker and development use the same workflow.
- 2025-12-04: Centralized install-mode metadata in `htdocs/_data/install_modes.yml`, normalized the string keys in `devices.html`, and let the template honor shared URLs/labels plus per-device overrides instead of duplicating the logic.
- 2025-12-04: Reworked the `/devices` compatibility column to show a descriptive level (Generally/Highly compatible) while keeping the scoring logic hidden; the thresholds are now documented in `README.md`.
- 2025-12-04: Implemented the compatibility scoring table inside `htdocs/_i18n/en/pages/devices.html`, scoring each device on bootloader state, build type, certification status, Play Integrity, Rootbeer results, and channel weight, while FP6 and Sunfish now expose the input map.
- 2025-12-04: Centralized install-mode metadata in `htdocs/_data/install_modes.yml`, normalized the string keys in `devices.html`, and let the template honor shared URLs/labels plus per-device overrides instead of duplicating the logic.
- 2025-12-04: Added `scripts/build_site.rb` to read the JSON-based config, echo the `VERSION`, respect `LANGUAGES` overrides, emit `dynamic/selected-languages.yml`, log failures into `build_site.err.txt`, and run Jekyll through the generated config so Docker and development use the same workflow.
- 2025-12-03: Noted that the public Docker image `jekyll/jekyll:4` is the supported tag that bundles the Jekyll 4.4.1 runtime required for this project.
- 2025-12-03: Documented the docker commands (`docker run --rm -v "$PWD/htdocs:/srv/jekyll" -p 4000:4000 -it jekyll/jekyll:4 jekyll serve`) to regenerate the Gemfile.lock and to serve the site locally for validation after dependency bumps.
- 2025-12-03: Adopted `hadolint` (via `docker run --rm -i hadolint/hadolint < Dockerfile.jekyll`) as the targeted lint step for Dockerfile updates and combined related RUN commands to satisfy rule DL3059.
+8 −1
Original line number Diff line number Diff line
@@ -76,7 +76,7 @@ docker run --rm \
Every run of `scripts/build_site.rb` clears `build_site.err.txt` and appends only fatal log entries with timestamps. Inspect this file before making changes so you can resolve the most recent failure faster.

## Compatibility scoring
The `/devices` page now computes an aggregated compatibility score instead of relying solely on Safetynet/Rootbeer flags. Each device entry in `htdocs/_data/devices` can supply the following `compatibility` map, and those values are rendered directly inside `htdocs/_i18n/en/pages/devices.html`:
The `/devices` page now computes an aggregated compatibility score but displays only a descriptive level (Generally/Highly compatible) for clarity. Each device entry in `htdocs/_data/devices` can supply the following `compatibility` map, and those values are rendered directly inside `htdocs/_i18n/en/pages/devices.html` to produce the level:

| Key | Points | Notes |
|---|---|---|
@@ -87,6 +87,13 @@ The `/devices` page now computes an aggregated compatibility score instead of re
| `rootbeer` | `pass`: 1, others: 0 | Falls back to the existing `rootbeer_pass` value when the map is absent. |
| `channel` | `official`: 2, `community`: 1, others: 0 | Defaults to `community`. |

The calculated score maps to three levels:

| Score range | Displayed level |
|---|---|
| `<= 5` | Generally compatible |
| `> 5` | Highly compatible |

Example data for FP6 and Sunfish are already present inside their respective YAML files. Devices without the map still render a score (using the defaults above) so the column never breaks.

## File Layout
+48 −109
Original line number Diff line number Diff line
@@ -233,6 +233,53 @@
            {%- assign isVerifiedBoot = 'yes' -%}
            {% endif %}
          {% endfor %}

          {%- assign compatibility = device.compatibility | default: {} -%}
          {%- assign score = 0 -%}
          {%- assign bootloader_value = compatibility.bootloader | default: '' -%}
          {%- if bootloader_value == 'locked_green' -%}
          {%- assign score = score | plus: 2 -%}
          {%- elsif bootloader_value == 'locked_yellow' -%}
          {%- assign score = score | plus: 1 -%}
          {%- endif -%}

          {%- assign build_type_value = compatibility.build_type | default: '' | downcase -%}
          {%- if build_type_value == 'user' -%}
          {%- assign score = score | plus: 1 -%}
          {%- endif -%}

          {%- assign google_value = compatibility.google_certified -%}
          {%- if google_value == true or google_value == 'true' -%}
          {%- assign score = score | plus: 1 -%}
          {%- endif -%}

          {%- assign play_value = compatibility.play_integrity | default: '' | downcase -%}
          {%- case play_value -%}
          {%- when 'basic' -%}
          {%- assign score = score | plus: 1 -%}
          {%- when 'device' -%}
          {%- assign score = score | plus: 2 -%}
          {%- when 'strong' -%}
          {%- assign score = score | plus: 3 -%}
          {%- endcase -%}

          {%- assign rootbeer_value = compatibility.rootbeer | default: '' -%}
          {%- if rootbeer_value == '' -%}
          {%- assign score = score | plus: 1 -%}
          {%- endif -%}

          {%- assign channel_value = compatibility.channel | default: 'community' | downcase -%}
          {%- if channel_value == 'official' -%}
          {%- assign score = score | plus: 2 -%}
          {%- elsif channel_value == 'community' -%}
          {%- assign score = score | plus: 1 -%}
          {%- endif -%}

          {%- assign level_label = 'Generally compatible' -%}
          {%- if score > 5 -%}
          {%- assign level_label = 'Highly compatible' -%}
          {%- endif -%}

          <tr data-device-type="{{ device.type }}" data-legacy="{{ device.legacy }}"
            data-verified-boot="{{ isVerifiedBoot }}">
            <td class="brand">{{ vendor.name }}</td>
@@ -300,116 +347,8 @@
              </ul>
            </td>
            <td class="compatibility">
              {%- assign compatibility = device.compatibility | default: {} -%}
              {%- assign score = 0 -%}

              {%- assign bootloader_value = compatibility.bootloader | default: '' -%}
              {%- assign bootloader_label = 'Unknown' -%}
              {%- assign bootloader_score = 0 -%}
              {%- case bootloader_value -%}
              {%- when 'locked_green' -%}
              {%- assign bootloader_label = 'Locked (green)' -%}
              {%- assign bootloader_score = 2 -%}
              {%- when 'locked_yellow' -%}
              {%- assign bootloader_label = 'Locked (yellow)' -%}
              {%- assign bootloader_score = 1 -%}
              {%- when 'unlockable' -%}
              {%- assign bootloader_label = 'Unlockable' -%}
              {%- else -%}
              {%- unless bootloader_value == nil or bootloader_value == '' -%}
              {%- assign bootloader_label = bootloader_value | replace: '_', ' ' | capitalize -%}
              {%- endunless -%}
              {%- endcase -%}
              {%- assign score = score | plus: bootloader_score -%}

              {%- assign build_type_value = compatibility.build_type | default: '' | downcase -%}
              {%- assign build_type_label = 'Unknown' -%}
              {%- assign build_type_score = 0 -%}
              {%- case build_type_value -%}
              {%- when 'user' -%}
              {%- assign build_type_label = 'User' -%}
              {%- assign build_type_score = 1 -%}
              {%- when 'userdebug' -%}
              {%- assign build_type_label = 'Userdebug' -%}
              {%- assign build_type_score = 0 -%}
              {%- else -%}
              {%- if build_type_value == '' -%}
              {%- assign build_type_label = 'Not declared' -%}
              {%- else -%}
              {%- assign build_type_label = build_type_value -%}
              {%- endif -%}
              {%- endcase -%}
              {%- assign score = score | plus: build_type_score -%}

              {%- assign google_value = compatibility.google_certified -%}
              {%- assign google_label = 'No' -%}
              {%- assign google_score = 0 -%}
              {%- if google_value == true or google_value == 'true' -%}
              {%- assign google_label = 'Yes' -%}
              {%- assign google_score = 1 -%}
              {%- endif -%}
              {%- assign score = score | plus: google_score -%}

              {%- assign play_value = compatibility.play_integrity | default: '' | downcase -%}
              {%- assign play_label = 'None' -%}
              {%- assign play_score = 0 -%}
              {%- case play_value -%}
              {%- when 'basic' -%}
              {%- assign play_label = 'Basic' -%}
              {%- assign play_score = 1 -%}
              {%- when 'device' -%}
              {%- assign play_label = 'Device' -%}
              {%- assign play_score = 2 -%}
              {%- when 'strong' -%}
              {%- assign play_label = 'Strong' -%}
              {%- assign play_score = 3 -%}
              {%- else -%}
              {%- if play_value == 'none' or play_value == '' -%}
              {%- assign play_label = 'None' -%}
              {%- else -%}
              {%- assign play_label = play_value -%}
              {%- endif -%}
              {%- endcase -%}
              {%- assign score = score | plus: play_score -%}

              {%- assign rootbeer_value = compatibility.rootbeer | default: '' -%}
              {%- if rootbeer_value == '' -%}
              {%- if device.rootbeer_pass == 1 -%}
              {%- assign rootbeer_value = 'pass' -%}
              {%- else -%}
              {%- assign rootbeer_value = 'fail' -%}
              {%- endif -%}
              {%- endif -%}
              {%- assign rootbeer_label = rootbeer_value | capitalize -%}
              {%- assign rootbeer_score = 0 -%}
              {%- if rootbeer_value == 'pass' -%}
              {%- assign rootbeer_score = 1 -%}
              {%- endif -%}
              {%- assign score = score | plus: rootbeer_score -%}

              {%- assign channel_value = compatibility.channel | default: 'community' | downcase -%}
              {%- assign channel_label = channel_value | capitalize -%}
              {%- assign channel_score = 0 -%}
              {%- case channel_value -%}
              {%- when 'official' -%}
              {%- assign channel_score = 2 -%}
              {%- when 'community' -%}
              {%- assign channel_score = 1 -%}
              {%- else -%}
              {%- assign channel_score = 0 -%}
              {%- endcase -%}
              {%- assign score = score | plus: channel_score -%}

              <div class="compatibility-score">
                <strong>Compatibility score: {{ score }}/10</strong>
                <ul class="list-unstyled compatibility-details">
                  <li>Bootloader: {{ bootloader_label }} ({{ bootloader_score }} pts)</li>
                  <li>Build: {{ build_type_label }} ({{ build_type_score }} pts)</li>
                  <li>Google certified: {{ google_label }} ({{ google_score }} pt{% if google_score != 1 %}s{% endif %})</li>
                  <li>Play Integrity: {{ play_label }} ({{ play_score }} pts)</li>
                  <li>Rootbeer: {{ rootbeer_label }} ({{ rootbeer_score }} pt{% if rootbeer_score != 1 %}s{% endif %})</li>
                  <li>Channel: {{ channel_label }} ({{ channel_score }} pts)</li>
                </ul>
                <span class="compatibility-level">{{ level_label }}</span>
              </div>
            </td>
            <td class="install">

scripts/build_site.rb

0 → 100644
+57 −0
Original line number Diff line number Diff line
#!/usr/bin/env ruby
require 'json'
require 'fileutils'
require 'time'

ROOT = File.expand_path('..', __dir__)
ERR_FILE = File.join(ROOT, 'build_site.err.txt')

def log_exception(exception)
  File.open(ERR_FILE, 'a') do |io|
    io.puts("#{Time.now.utc.iso8601} ERROR: #{exception.class}: #{exception.message}")
    exception.backtrace&.each { |line| io.puts("  #{line}") }
  end
end

File.write(ERR_FILE, '')

begin
  params = JSON.parse(File.read(File.join(ROOT, 'config', 'param.json')))

  version = params.fetch('VERSION')
  default_languages = params.fetch('LANGUAGES')
  output_dir = ENV.fetch('BUILD_OUTPUT_DIR', params.fetch('BUILD_OUTPUT_DIR'))
  jekyll_config = ENV.fetch('JEKYLL_CONFIG', params.fetch('JEKYLL_CONFIG'))

  env_languages = ENV['LANGUAGES']&.strip
  languages = if env_languages && !env_languages.empty?
                env_languages.split(',').map(&:strip).reject(&:empty?)
              else
                default_languages
              end

  raise 'no languages configured via LANGUAGES or config' if languages.empty?

  puts "Documentation build version #{version}"
  puts "Languages: #{languages.join(', ')}"

  dynamic_dir = File.join(ROOT, 'dynamic')
  FileUtils.mkdir_p(dynamic_dir)
  language_config_path = File.join(dynamic_dir, 'selected-languages.yml')
  File.open(language_config_path, 'w') do |file|
    file.puts('languages:')
    languages.each { |language| file.puts("  - #{language}") }
  end

  cmd_config = "#{jekyll_config},#{language_config_path}"
  jekyll_cmd = ['jekyll', 'build', '-d', output_dir, '--config', cmd_config]

  puts "Running: #{jekyll_cmd.join(' ')}"
  success = system(*jekyll_cmd)

  raise "jekyll build failed with exit code #{$?.exitstatus}" unless success
rescue StandardError => error
  log_exception(error)
  warn "Build failed: #{error.message} (see build_site.err.txt)"
  raise
end