Skip to content

docs(changelog): credit the compose button scrollbar fix in 1.0.1.3 #965

docs(changelog): credit the compose button scrollbar fix in 1.0.1.3

docs(changelog): credit the compose button scrollbar fix in 1.0.1.3 #965

Workflow file for this run

name: CI
on:
push:
branches: [master, dev]
pull_request:
branches: [master, dev]
permissions:
contents: read
jobs:
# Cheap gates that don't need the SDK or Dalamud; run them first so a
# version-sync mistake or a style break fails fast without burning the
# full build.
guards:
name: Guards
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Verify version sync (Directory.Build.props ↔ repo.json)
shell: bash
run: |
build_ver=$(grep -oP '<Version>\K[\d.]+(?=</Version>)' Directory.Build.props)
assembly_ver=$(jq -r '.[0].AssemblyVersion' repo.json)
testing_ver=$(jq -r '.[0].TestingAssemblyVersion' repo.json)
echo "Directory.Build.props <Version> = $build_ver"
echo "repo.json AssemblyVersion = $assembly_ver"
echo "repo.json TestingAssemblyVersion = $testing_ver"
if [ "$build_ver" != "$assembly_ver" ] || [ "$build_ver" != "$testing_ver" ]; then
echo "::error::Version mismatch: Directory.Build.props ($build_ver), repo.json AssemblyVersion ($assembly_ver), TestingAssemblyVersion ($testing_ver) must all match."
exit 1
fi
- name: Verify UI scale seam
shell: bash
run: |
offenders=$(grep -rn 'ImGuiHelpers\.GlobalScale' src --include=*.cs \
| grep -v 'src/Aetherphone/UiScale.cs' || true)
if [ -n "$offenders" ]; then
echo "$offenders"
echo "::error::Draw code must scale through UiScale.Current, not ImGuiHelpers.GlobalScale directly. Only src/Aetherphone/UiScale.cs may read the Dalamud scale."
exit 1
fi
# The dash is built from its UTF-8 bytes so this step does not trip itself,
# and announce-commits.yml is exempt because it carries the character to
# substitute it out of Discord payloads.
- name: Verify no em dashes
shell: bash
run: |
dash=$(printf '\xe2\x80\x94')
offenders=$(git ls-files -z | xargs -0 grep -I -H -n -F -- "$dash" \
| grep -v '^.github/workflows/announce-commits.yml:' || true)
if [ -n "$offenders" ]; then
echo "$offenders"
echo "::error::No em dashes anywhere: not in code, UI strings, locale JSONs, or docs. Use commas, colons, or parentheses."
exit 1
fi
- name: Verify no async void
shell: bash
run: |
offenders=$(grep -rn 'async void' src --include=*.cs || true)
if [ -n "$offenders" ]; then
echo "$offenders"
echo "::error::Zero async void. Return async Task, and fire-and-forget from draw code with an explicit discard: _ = Task.Run(...)."
exit 1
fi
# System.Linq is removed from the implicit usings in Aetherphone.csproj, so
# LINQ cannot be reached without the using line below appearing in a file.
# That makes this list the whole LINQ surface of the plugin rather than a
# sample of operator names, which is what the previous grep checked: it
# named three files while four others were using operators it never listed.
- name: Verify no new LINQ
shell: bash
run: |
offenders=$(grep -rln 'using System\.Linq;' src/Aetherphone --include=*.cs \
| grep -v '^src/Aetherphone/Apps/Calendar/CalendarEvents.cs$' \
| grep -v '^src/Aetherphone/Apps/Music/MusicApp.cs$' \
| grep -v '^src/Aetherphone/Apps/Photos/PhotosApp.Grid.cs$' \
| grep -v '^src/Aetherphone/Apps/Velvet/VelvetFilterSelection.cs$' \
| grep -v '^src/Aetherphone/Apps/Velvet/VelvetNotInterestedArchive.cs$' \
| grep -v '^src/Aetherphone/Apps/Velvet/VelvetStore.cs$' \
| grep -v '^src/Aetherphone/Core/Radio/RadioPlayer.cs$' \
| grep -v '^src/Aetherphone/Core/Songs/SongPlayer.cs$' || true)
if [ -n "$offenders" ]; then
echo "$offenders"
echo "::error::No LINQ in this tree: it allocates iterators and delegates on every call and the whole UI redraws every frame. Write a for loop, or add the file here if the path is genuinely cold."
exit 1
fi
- name: Verify clock format seam
shell: bash
run: |
offenders=$(grep -rnE '"(HH|hh|H|h):mm' src --include=*.cs \
| grep -v 'src/Aetherphone/Core/Localization/TimeText.cs' || true)
if [ -n "$offenders" ]; then
echo "$offenders"
echo "::error::All clock text goes through TimeText.Clock. Only src/Aetherphone/Core/Localization/TimeText.cs may hand-format a time pattern."
exit 1
fi
# Core holds services and domain, Windows holds the shared toolkit, and Apps
# are leaf features that consume both. AppRegistry and WidgetCatalog are the
# two composition roots, so they are the only places allowed to name a leaf.
- name: Verify layering
shell: bash
run: |
offenders=$(grep -rn '^using Aetherphone\.Apps' src/Aetherphone/Core src/Aetherphone/Windows --include=*.cs \
| grep -v '^src/Aetherphone/Core/Apps/AppRegistry.cs:' \
| grep -v '^src/Aetherphone/Windows/Widgets/WidgetCatalog.cs:' || true)
if [ -n "$offenders" ]; then
echo "$offenders"
echo "::error::Core and the Windows toolkit must not reach into an app. Invert onto an interface in Core, or move the shared piece down."
exit 1
fi
# A naming analyzer cannot express this: a private static readonly holding a
# lookup table is PascalCase while one holding mutable state is camelCase,
# so any capitalization rule fires on about a thousand correct names. The
# leading underscore is the part that is unambiguous.
- name: Verify no underscore prefixed fields
shell: bash
run: |
offenders=$(grep -rnE '^[[:space:]]*(private|protected)[[:alnum:][:space:]<>,?\[\]\.]*[[:space:]]_[a-zA-Z]' \
src/Aetherphone src/Aetherphone.Tests --include=*.cs || true)
if [ -n "$offenders" ]; then
echo "$offenders"
echo "::error::Fields carry no leading underscore. Name the field for what it holds instead."
exit 1
fi
# A marker on the first line hides the namespace from the check below, so
# a correct file gets reported as having no namespace at all and the error
# sends you looking in the wrong place. Catch the marker itself and name
# it. .editorconfig asks for utf-8, not utf-8-bom.
- name: Verify no byte order marks
shell: bash
run: |
offenders=$(grep -rlI $'^\xef\xbb\xbf' src/Aetherphone src/Aetherphone.Tests --include=*.cs || true)
if [ -n "$offenders" ]; then
echo "$offenders"
echo "::error::Files carry no byte order mark. Save as utf-8 without one, the way .editorconfig asks."
exit 1
fi
# ManagedDoom is a vendored port and keeps its own layout.
#
# Windows/Components is the one deliberate exception. Its folders group 182
# files by kind so the toolkit is navigable, while every file keeps the flat
# Aetherphone.Windows.Components namespace so consuming it stays one import.
# Sub-namespacing was measured first: it would have added 792 using lines
# across 424 files, and the files that draw the most would have needed five
# to nine imports each. The rule for that subtree is therefore the inverse,
# every file declares exactly the one namespace, and it is checked as such.
- name: Verify namespace matches folder
shell: bash
run: |
toolkit='^src/Aetherphone/Windows/Components/'
offenders=""
while IFS= read -r file; do
actual=$(grep -m1 -oP '^namespace \K[A-Za-z0-9_.]+(?=\s*;)' "$file" || true)
if [[ "$file" =~ $toolkit ]]; then
expected="Aetherphone.Windows.Components"
else
expected=$(dirname "${file#src/}" | tr '/' '.')
fi
if [ -z "$actual" ]; then
offenders+="$file: no file-scoped namespace"$'\n'
elif [ "$actual" != "$expected" ]; then
offenders+="$file: declares $actual, expected $expected"$'\n'
fi
done < <(git ls-files -- src | grep '\.cs$' | grep -v '^src/ManagedDoom/')
if [ -n "$offenders" ]; then
printf '%s' "$offenders"
echo "::error::A file-scoped namespace must match the folder path, except under Windows/Components which is one flat namespace. Move the file or fix the namespace."
exit 1
fi
# Full build. Needs Dalamud present at the standard plugin path so
# Dalamud.NET.Sdk can resolve $(DalamudLibPath).
build:
name: Build (Windows)
needs: guards
runs-on: windows-latest
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Set up .NET
uses: actions/setup-dotnet@v5
with:
dotnet-version: '10.0.x'
- name: Download Dalamud
shell: pwsh
run: |
$dalamudUrl = "https://goatcorp.github.io/dalamud-distrib/latest.zip"
$dalamudDir = "$env:AppData\XIVLauncher\addon\Hooks\dev"
New-Item -ItemType Directory -Force -Path $dalamudDir | Out-Null
Invoke-WebRequest -Uri $dalamudUrl -OutFile dalamud.zip
Expand-Archive -Path dalamud.zip -DestinationPath $dalamudDir -Force
Remove-Item dalamud.zip
- name: Restore
run: dotnet restore Aetherphone.sln --locked-mode
- name: Build (Release)
run: dotnet build Aetherphone.sln --configuration Release --no-restore
- name: Run tests
run: >
dotnet test Aetherphone.sln --configuration Release --no-build
--logger "console;verbosity=normal"
--logger "trx;LogFileName=test-results.trx"
--results-directory TestResults
# The console log on a Windows runner is long and the assertion is buried
# in it, so the trx goes up whenever the suite is red.
- name: Upload test results
if: failure()
uses: actions/upload-artifact@v7
with:
name: test-results
path: TestResults/test-results.trx
if-no-files-found: warn
retention-days: 14
# Every pull request gets a loadable build: reviewing this plugin means
# opening the phone in game, so the reviewer needs the zip, not a log.
- name: Upload plugin artifact
uses: actions/upload-artifact@v7
with:
name: Aetherphone-plugin
path: src/Aetherphone/bin/Release/Aetherphone/latest.zip
if-no-files-found: error
retention-days: 14