Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions .github/workflows/auto-draft-pr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

name: Convert PRs to Draft on Opening
name: Pull Request preparation

permissions:
contents: write
Expand All @@ -31,7 +31,22 @@ jobs:
convert_to_draft:
runs-on: ubuntu-latest
steps:
- run: gh pr ready --undo ${{ github.event.pull_request.number }}
- name: Add snippet
working-directory: changelog/snippets
run: |
# Configure git
git config user.email "github@faforever.com"
git config user.name "FAForever Machine User"

FILE=category.${{ github.event.pull_request.number }}.md
cp sections/template-snippet.md $FILE
sed -i "s/XYZW/${{ github.event.pull_request.number }}/g" $FILE

git add .
git commit -m "Add snippet template"

Comment on lines +34 to +47
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Workflow signals:"
rg -n 'actions/checkout|working-directory|FILE=|git commit|git push' .github/workflows/auto-draft-pr.yaml

echo
echo "Bundler accepted snippet categories:"
rg -n 'process_snippets "' .github/workflows/scripts/bash/changelog-combine.sh

Repository: FAForever/fa

Length of output: 693


🏁 Script executed:

cat -n .github/workflows/auto-draft-pr.yaml | head -50

Repository: FAForever/fa

Length of output: 2288


Workflow will fail: missing checkout, no push, and invalid filename pattern.

The job is missing a checkout step (line 34 tries to access fa/changelog/snippets without cloning the repo first), never pushes the commit (line 46), and uses category.<PR>.md which the bundler doesn't recognize—it only processes balance.*.md, features.*.md, fix.*.md, graphics.*.md, ai.*.md, performance.*.md, or other.*.md.

Suggested fix
+      - name: Checkout PR branch
+        uses: actions/checkout@v4
+        with:
+          ref: ${{ github.event.pull_request.head.ref }}

       - name: Add snippet
         working-directory: fa/changelog/snippets
         run: |
           # Configure git
           git config user.email "github@faforever.com"
           git config user.name "FAForever Machine User"
           
-          FILE=category.${{ github.event.pull_request.number }}.md
+          FILE=other.${{ github.event.pull_request.number }}.md
           cp sections/template-snippet.md $FILE
           sed -i "s/XYZW/${{ github.event.pull_request.number }}/g" $FILE
           
-          git add .
-          git commit -m "Add snippet template"
+          git add "$FILE"
+          git commit -m "Add snippet template" || true
+          git push
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/auto-draft-pr.yaml around lines 34 - 47, The workflow step
that creates and commits a snippet is missing a repository checkout, a push of
the new commit, and uses an unrecognized filename pattern; to fix it add an
initial "actions/checkout@vX" step before changing into fa/changelog/snippets,
update FILE from category.${{ github.event.pull_request.number }}.md to one of
the accepted patterns (e.g. other.${{ github.event.pull_request.number }}.md or
features.${{ github.event.pull_request.number }}.md) so the bundler will pick it
up, and after git commit run git push to push the new branch/commit; ensure the
existing commands that reference FILE, cp sections/template-snippet.md, sed -i
"s/XYZW/${{ github.event.pull_request.number }}/g" $FILE, git add ., and git
commit -m "Add snippet template" remain but run only after checkout and before
the git push.

- name: Convert PR to draft
run: gh pr ready --undo ${{ github.event.pull_request.number }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
1 change: 1 addition & 0 deletions changelog/snippets/sections/template-snippet.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Your explanation here (#XYZW).
4 changes: 4 additions & 0 deletions docs/Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,7 @@ gem "jekyll", "~> 4.3.3" # installed by `gem jekyll`

gem "just-the-docs", "0.8.2" # pinned to the current release
# gem "just-the-docs" # always download the latest release

gem "csv", "~> 3.3"

gem "base64", "~> 0.3.0"
10 changes: 7 additions & 3 deletions docs/Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,24 @@ GEM
specs:
addressable (2.8.6)
public_suffix (>= 2.0.2, < 6.0)
base64 (0.3.0)
bigdecimal (3.1.8)
colorator (1.1.0)
concurrent-ruby (1.2.3)
csv (3.3.5)
em-websocket (0.5.3)
eventmachine (>= 0.12.9)
http_parser.rb (~> 0)
eventmachine (1.2.7)
ffi (1.16.3)
forwardable-extended (2.6.0)
google-protobuf (4.27.5-arm64-darwin)
google-protobuf (4.33.6-arm64-darwin)
bigdecimal
rake (>= 13)
google-protobuf (4.27.5-x64-mingw-ucrt)
google-protobuf (4.33.6-x64-mingw-ucrt)
bigdecimal
rake (>= 13)
google-protobuf (4.27.5-x86_64-linux)
google-protobuf (4.33.6-x86_64-linux-gnu)
bigdecimal
rake (>= 13)
http_parser.rb (0.8.0)
Expand Down Expand Up @@ -89,6 +91,8 @@ PLATFORMS
x86_64-linux-gnu

DEPENDENCIES
base64 (~> 0.3.0)
csv (~> 3.3)
jekyll (~> 4.3.3)
just-the-docs (= 0.8.2)

Expand Down
13 changes: 13 additions & 0 deletions docs/_plugins/balance_change.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#module Jekyll
# class BalanceChangeBlock < Jekyll::Hooks
# end
#end

Jekyll::Hooks.register [:pages, :documents], :post_render do |doc|

doc.output.gsub!(
/([^<\s]+)\s*--&gt;\s*([^<\s]+)/,
'<span class="old">\1</span> → <span class="new">\2</span>'
Comment on lines +8 to +10
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Regex misses common value formats with spaces/parentheses.

This pattern only matches single tokens, so examples like 60 (225) --> 45 (225) won’t be transformed.

🔧 Suggested patch
-    /([^<\s]+)\s*--&gt;\s*([^<\s]+)/,
+    /([^<]+?)\s*--&gt;\s*([^<]+?)(?=\s*(?:<|$))/,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
doc.output.gsub!(
/([^<\s]+)\s*--&gt;\s*([^<\s]+)/,
'<span class="old">\1</span> → <span class="new">\2</span>'
doc.output.gsub!(
/([^<]+?)\s*--&gt;\s*([^<]+?)(?=\s*(?:<|$))/,
'<span class="old">\1</span> → <span class="new">\2</span>'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/_plugins/balance_change.rb` around lines 8 - 10, The current regex in
doc.output.gsub! (the /([^<\s]+)\s*--&gt;\s*([^<\s]+)/) only captures single
tokens (no spaces or parentheses), so multi-token values like "60 (225) --> 45
(225)" are missed; update the pattern to allow any non-HTML content around the
arrow (e.g. use lazy captures like /([^<]+?)\s*--&gt;\s*([^<]+?)/ or similar)
and trim captured groups before inserting them into the replacement span so
leading/trailing whitespace isn't preserved; keep the replacement logic in the
same gsub! call but switch to the broader capture groups (referencing
doc.output.gsub! and the regex literal) and ensure you still escape
HTML-sensitive chars if needed.

)

end
23 changes: 23 additions & 0 deletions docs/_plugins/unit_block.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
module Jekyll
class UnitBlock < Liquid::Block

def initialize(tag_name, markup, tokens)
super
@unit_id = markup.strip
end

def render(context)
name = super.strip

<<~HTML
<div class="unit-header" data-unit="#{@unit_id}">
<img class="unit-icon"
src="/assets/icons/#{@unit_id}.png">
<span class="unit-name">#{name}</span>
Comment on lines +13 to +16
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Escape and validate interpolated HTML values.

@unit_id and name are injected raw into HTML. This should be escaped (and unit_id validated) before rendering.

🛡️ Suggested patch
+require "cgi"
+
 module Jekyll
   class UnitBlock < Liquid::Block
@@
     def render(context)
-      name = super.strip
+      name = CGI.escapeHTML(super.strip)
+      unit_id = `@unit_id.strip`
+      raise ArgumentError, "Invalid unit id" unless unit_id.match?(/\A[a-zA-Z0-9_\/-]+\z/)
+      safe_unit_id = CGI.escapeHTML(unit_id)

       <<~HTML
-      <div class="unit-header" data-unit="#{`@unit_id`}">
+      <div class="unit-header" data-unit="#{safe_unit_id}">
         <img class="unit-icon"
-             src="/assets/icons/#{`@unit_id`}.png">
+             src="/assets/icons/#{safe_unit_id}.png"
+             alt="">
         <span class="unit-name">#{name}</span>
       </div>
       HTML
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/_plugins/unit_block.rb` around lines 13 - 16, The template injects raw
`@unit_id` and name into HTML; update the UnitBlock rendering to validate `@unit_id`
against a strict whitelist/regex (e.g., only [A-Za-z0-9_-]) and reject or
normalize unexpected values, and escape any user-provided text using an
HTML-escaping helper (e.g., CGI.escapeHTML or Jekyll::Utils.escape_html) when
outputting name and when composing the img src attribute; locate the renderer
method that outputs the div with class "unit-header" (the code using `@unit_id`
and name) and apply these validations/escapes so the
src="/assets/icons/#{`@unit_id`}.png" and the span content use safe, escaped
values.

</div>
HTML
end
end
end

Liquid::Template.register_tag('unit', Jekyll::UnitBlock)
31 changes: 31 additions & 0 deletions docs/_sass/custom/custom.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
.unit-header {
display: flex;
align-items: center;
gap: 10px;
margin-top: 2rem;
font-size: 1.2rem;
font-weight: 600;
}

.unit-icon {
width: 64px;
height: 64px;
}

.unit-name {
display: inline-block;
}

.unit-header + ul > li {
font-weight: 600;
}

.old {
color: #d73a49;
text-decoration: line-through;
}

.new {
color: #2da44e;
font-weight: 600;
}
Binary file added docs/assets/icons/XES0102.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 1 addition & 7 deletions docs/development-changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,9 @@ We use snippets to reduce the burden on maintainers to write an accurate changel

### Format of a snippet

All current snippets can be found in the [snippets folder](../changelog/snippets/).

The structure of the file name is `XXX.ABCD.md`, where `XXX` is one of the snippet types and `ABCD` is the pull request number. The available snippet types are `fix`, `features`, `balance`, `graphics`, `ai`, `performance` or `other`.

The content of a snippet is similar to a commit message. The first line is a title that starts with the relevant pull requests and a concise description of the changes, as an example: ` - (#PR1, #PR2, ...) <concise description of changes>`. Use a dot at the end of the first line. The remainder of the file can be used to provide additional and more detailed information about the changes. Remember to indent these additional lines, so they follow the indentation that gets created because of the list item of the first line. Add an empty line at the end of the file to make sure that the next snippet is separated by an empty line. You can make use of a Markdown formatter to ensure consistency, one example is the use of [prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode).
The format of a snippet depends on whether it is a [balance snippet](../development/changelog/balance-snippet) or [any other category](../development/changelog/other-snippet).

### Choosing a category

Expand All @@ -38,10 +36,6 @@ The content of a snippet is similar to a commit message. The first line is a tit

If multiple categories are fitting, use the one that appears first in this list.

### Example snippet



## Sources and inspiration

We did not come up with this approach ourselves. We took inspiration from similar solutions of projects that experienced similar problems:
Expand Down
56 changes: 45 additions & 11 deletions docs/development-changelog/balance-snippet.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,57 @@ permalink: development/changelog/balance-snippet
published: true
---

_Information about how to structure a balance snippet_
The game just renders the text as is, so we need to keep that in mind.
Unit or explanation first?
The unitID is also used to infer if the snippet is supposed to go into the land, navy, air, structures or enhancements category.
if the change is an enhancement, the unitID is enhancements/faction/name
The md to lua converter should remove the {: .unit-change} and the (unitID)

Comment on lines +9 to +14
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove draft notes before publishing this doc page.

These lines read like internal brainstorming/TODO notes and conflict with the otherwise polished guidance.

🧾 Suggested cleanup
-The game just renders the text as is, so we need to keep that in mind.
-Unit or explanation first?
-The unitID is also used to infer if the snippet is supposed to go into the land, navy, air, structures or enhancements category.
-if the change is an enhancement, the unitID is enhancements/faction/name
-The md to lua converter should remove the {: .unit-change} and the (unitID)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
The game just renders the text as is, so we need to keep that in mind.
Unit or explanation first?
The unitID is also used to infer if the snippet is supposed to go into the land, navy, air, structures or enhancements category.
if the change is an enhancement, the unitID is enhancements/faction/name
The md to lua converter should remove the {: .unit-change} and the (unitID)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/development-changelog/balance-snippet.md` around lines 9 - 14, Remove
the draft/internal notes lines in the balance-snippet content (the brainstorming
lines about "Unit or explanation first?", "The unitID is also used...", etc.) so
the page reads as polished guidance; also ensure the md->lua converter strips
the inline marker "{: .unit-change}" and removes the trailing "(unitID)" tokens
(including cases like "enhancements/faction/name") from rendered output so no
draft markers or raw unit IDs remain in published docs.



The snippet file should be named `balance.<PR Number>.md`.

```
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix markdownlint issues in examples section.

Line 19 should specify a fenced language, and Line 38 should not jump heading levels.

🧹 Suggested patch
-```
+```md
@@
-### Example snippets
+## Example snippets

Also applies to: 38-38

🧰 Tools
🪛 markdownlint-cli2 (0.22.0)

[warning] 19-19: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/development-changelog/balance-snippet.md` at line 19, Add a fenced
language specifier to the code block starting at the opening ``` on line 19 by
changing it to ```md, and fix the heading level by replacing the "### Example
snippets" heading with "## Example snippets" (update the heading text referenced
as "### Example snippets" to "## Example snippets") so markdownlint no longer
reports a missing fence language and a skipped heading level.

- (#<PR Number>) <Description>

**<Formatted Unit Name> (<Blueprint ID>):**
- <Section>
- <Parameter Name>: <value before> --> <value after>
- <Parameter Name>: <value before> --> <value after>
{% unit <BlueprintID> %}
<Formatted Unit Name>
{% endunit %}
<Description> (#<PR Number>)
- <Category>:
- <Parameter Name>: <value before> --> <value after>
```

- PR Number: The number of the pull request on GitHub.
- Description: A 1-sentence summary of the changes and then a short explanation behind the reasoning for the changes.
- Formatted Unit Name: `<UnitName>: <Tech> <Unit Description>` For example: `Exodus: T2 Destroyer`. This is similar to the format visible in unit dbs and the in-game UI when hovering over a unit.
- Blueprint ID: Blueprint ID of the unit in uppercase.
- Section: An optional subheader to categorize parameters. Usually a blueprint subtable name (ex: Physics) or a weapon name.
- Parameter Name: The name for the value that was changed. It shouldn't be the exact blueprint field name; it should be a name that players can understand and formatted like normal text.
- Formatted Unit Name: `<UnitName>: <Tech> <Unit Role>` For example: `Exodus: T2 Destroyer`. This is similar to the format visible in unit dbs and the in-game UI when hovering over a unit.
- Blueprint ID: Blueprint ID of the unit.
- Category: A subheader to categorize parameters. Usually a blueprint subtable name (ex: Physics) or a weapon name.
- Parameter Name: The name for the value that was changed. It doesn't need to be the exact blueprint field name; it should be a name that players can understand.
- Value before/after: The value before/after the change. If relevant, derived values like DPS can be put in parentheses after the value as such: `<damage> (<dps>) --> <damage> (<dps>)`.

When the same change has been applied to multiple units, you can just write the group in the title. The unitID still loads the unit icon, so choose a unit that is most representative. (UEF when all factions are affected).

### Example snippets

{% unit URS0201 %}
Salem Class: T2 Destroyer
{% endunit %}
Reduced Salem’s anti-torpedo flare target check interval from 1.0s to 0.4s—the standard for anti-projectile weapons. This improves torpedo detection and flare response, especially against torpedo bombers. In turn the movement speed has been tuned down (#6339).
- Anti Torpedo:
- Target Check Interval: 1s --> 0.4s
- Movement:
- Max speed : 5 --> 4

{% unit UEA0102 %}
All T1 Interceptors
{% endunit %}
Reduce the distance at which T1 Interceptors hover instead of turning when given a move order (#6342).
- Air Movement:
- Start Turn Distance: 10 --> 5

{% unit enhancements/cybran/torp %}
Nanite Torpedo Launcher
{% endunit %}
Further increase the MuzzleSalvoSize of the Cybran ACU's Nanite Torpedo upgrade to 4, as it still had difficulties penetrating torpedo defenses after (#6476) increased it to 3. Its DPS remains unchanged (#6542).
- Torpedo weapon:
- Damage (DPS): 60 (225) --> 45 (225)
- Muzzle Salvo Size: 3 --> 4
11 changes: 10 additions & 1 deletion docs/development-changelog/other-snippet.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,13 @@ permalink: development/changelog/other-snippet
published: true
---

_Information about how to structure all other snippets_
The content of a snippet that is not in the balance category is similar to a commit message. The first line is a title that starts with a dash followed by a concise description of the changes. Use a dot at the end of the first line. The remainder of the file can be used to provide additional and more detailed information about the changes. Remember to indent these additional lines, so they follow the indentation that gets created because of the list item of the first line. Mention the relevant pull requests at the end of the text like so: `(#PR1, #PR2, ...)`.
Add an empty line at the end of the file to make sure that the next snippet is separated by an empty line. You can make use of a Markdown formatter to ensure consistency, one example is the use of [prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode).

### Example snippet
```
Comment on lines +12 to +13
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix markdownlint violations in the example section.

Line 12 should not skip heading levels, and Line 13 should declare a fence language.

🧹 Suggested patch
-### Example snippet
-```
+## Example snippet
+```md
 - Tweak the formula used to calculate the level of detail (LOD) for props.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
### Example snippet
```
## Example snippet
🧰 Tools
🪛 markdownlint-cli2 (0.22.0)

[warning] 12-12: Heading levels should only increment by one level at a time
Expected: h2; Actual: h3

(MD001, heading-increment)


[warning] 13-13: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/development-changelog/other-snippet.md` around lines 12 - 13, The
markdown violates lint rules by skipping a heading level and omitting a fence
language: change the "### Example snippet" heading to "## Example snippet" and
add the language tag "md" to the opening code fence for the example (the fenced
block that contains the LOD formula line) so it reads as a proper fenced code
block with a declared language.

- Tweak the formula used to calculate the level of detail (LOD) for props.

Large props are visible for longer, and it should now be easier again to spot broken tree groups (#6906).

```
2 changes: 1 addition & 1 deletion docs/documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,4 @@ In addition we recommend the following tooling:
To run the documentations website on the localhost you need a command line where the current directory is the `docs` folder. From there you run the following commands:

- `bundle install`: Similar to `npm install` if you're familiar with the Node Package Manager. Should be necessary only once. Installs all relevant packages.
- `bundle exec jekyll serve --config _config.yml,_config_debug.ym`: Once completed the website should be available on your localhost. It runs the website from the `_site` folder. The website is updated automagically on every file change but it is not refreshed automagically. You'll need to refresh the page manually.
- `bundle exec jekyll serve --config _config.yml,_config_debug.yml`: Once completed the website should be available on your localhost. It runs the website from the `_site` folder. The website is updated automagically on every file change but it is not refreshed automagically. You'll need to refresh the page manually.
Loading