diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 314d7ced..14af8789 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -84,9 +84,18 @@ updates: # against immutable rule 11, which asks for a licence check on every one, and # against two gates that pin this graph on purpose. They are watched by a # scheduled job instead, which reports rather than proposes. O170. + # + # Grouped since 2026-10-14, when the file went from semgrep alone to every + # package it installs. Those move together - a semgrep release raises the + # floor of what it needs - and one pull request per package would leave + # each of them red against the others' old pins. - package-ecosystem: pip directory: "/.github" schedule: interval: weekly cooldown: default-days: 7 + groups: + semgrep: + patterns: + - "*" diff --git a/.github/requirements-semgrep.txt b/.github/requirements-semgrep.txt index fb6e0f95..13b968be 100644 --- a/.github/requirements-semgrep.txt +++ b/.github/requirements-semgrep.txt @@ -20,6 +20,82 @@ # # Licence read from the pinned version rather than recalled, on 2026-08-27: # LGPL-2.1-or-later, stated by the wheel metadata as a License-Expression and -# carried in full beside it. It analyses the tree and is never linked into -# either binary. -semgrep==1.178.0 +# carried in full beside it. Read again for 1.180.0 on 2026-10-14, the same. +# It analyses the tree and is never linked into either binary. +# +# Every package it installs is pinned below, not only semgrep, since +# 2026-10-14. With the one line alone the vulnerability scanner has to guess +# what pip would pick, and it guessed wrong: measured that day, a scan of +# "semgrep==1.180.0" reported eleven advisories against python-multipart +# 0.0.9, anyio 4.9.0 and idna 3.9.0, while pip on Python 3.14 installed +# 0.0.32, 4.15.1 and 3.20, and a scan of what it really installed found +# nothing. The list is `pip freeze` of a clean install on Linux with the +# Python the job uses, which is the only place it is installed. Dependabot +# reads every line, and carries them in one pull request - see the group in +# dependabot.yml. +annotated-types==0.8.0 +anyio==4.15.1 +attrs==26.1.0 +boltons==21.0.0 +bracex==3.1 +certifi==2026.7.22 +cffi==2.1.1 +charset-normalizer==3.5.2 +click==8.4.2 +click-option-group==0.5.9 +colorama==0.4.6 +cryptography==50.0.2 +exceptiongroup==1.2.2 +face==26.0.1 +glom==25.12.0 +googleapis-common-protos==1.75.5 +h11==0.16.0 +httpcore==1.0.9 +httpx==0.28.1 +httpx-sse==0.4.3 +idna==3.20 +importlib_metadata==8.7.1 +jsonschema==4.25.1 +jsonschema-specifications==2025.9.1 +markdown-it-py==4.2.0 +mcp==1.29.0 +mdurl==0.1.2 +opentelemetry-api==1.37.0 +opentelemetry-exporter-otlp-proto-common==1.37.0 +opentelemetry-exporter-otlp-proto-http==1.37.0 +opentelemetry-instrumentation==0.58b0 +opentelemetry-instrumentation-requests==0.58b0 +opentelemetry-instrumentation-threading==0.58b0 +opentelemetry-proto==1.37.0 +opentelemetry-sdk==1.37.0 +opentelemetry-semantic-conventions==0.58b0 +opentelemetry-util-http==0.58b0 +packaging==26.3 +peewee==3.19.0 +protobuf==6.33.6 +pycparser==3.11 +pydantic==2.14.1 +pydantic-settings==2.16.0 +pydantic_core==2.50.1 +Pygments==2.21.0 +PyJWT==2.15.1 +python-dotenv==1.2.4 +python-multipart==0.0.32 +referencing==0.37.0 +requests==2.34.2 +rich==15.0.0 +rpds-py==2026.9.1 +ruamel.yaml==0.19.1 +ruamel.yaml.clib==0.2.15 +semantic-version==2.10.0 +semgrep==1.180.0 +sse-starlette==3.5.0 +starlette==1.8.0 +tomli==2.4.1 +typing-inspection==0.4.4 +typing_extensions==4.16.0 +urllib3==2.8.0 +uvicorn==0.54.0 +wcmatch==8.5.2 +wrapt==1.17.3 +zipp==4.1.1 diff --git a/.github/scripts/dependency_gate.py b/.github/scripts/dependency_gate.py index fc496bca..7b8c6d08 100644 --- a/.github/scripts/dependency_gate.py +++ b/.github/scripts/dependency_gate.py @@ -43,9 +43,10 @@ # ABSENT on purpose. It is famously incompatible with GPL-3.0 and it is exactly # the kind of entry that looks fine in a list of open source licences and is not. # -# Every identifier below except the last was measured in this repository's own -# dependency graph on 2026-08-27, so the list describes what we actually have -# rather than what somebody expected us to have. +# Every identifier below except the last two was measured in this repository's +# own dependency graph on 2026-08-27, so the list describes what we actually have +# rather than what somebody expected us to have. The last two carry their own +# reasons beside them. ALLOWED = frozenset({ "0BSD", "Apache-2.0", "BSD-2-Clause", "BSD-3-Clause", "CC0-1.0", "ISC", "MIT", "MIT-0", "MPL-2.0", "Unlicense", "Zlib", @@ -60,6 +61,17 @@ # here. Without this entry three modules already in the tree would be # blocked the next time they moved. "LicenseRef-scancode-google-patent-license-golang", + + # Fonts. GitHub reports fyne.io/fyne/v2 as "BSD-3-Clause AND OFL-1.1", + # measured on 2026-10-14 for both v2.8.1 and v2.8.2, because the toolkit + # carries Noto Sans and Inter Symbols in theme/font. Both are already + # compiled into the window and named in THIRD-PARTY-NOTICES.md with the + # full licence text, beside the Inter this project embeds itself. The OFL + # permits a font to be bundled with any software, and a font is data the + # program draws with rather than code linked into it, so it puts nothing + # on the GPL-3.0 code around it. Recorded here on the day the toolkit + # moved, which was the first time the gate saw it. + "OFL-1.1", }) # Packages whose licence GitHub cannot resolve and which a person has already @@ -94,11 +106,25 @@ # refuses to let slide. That guard is what makes this skip honest. SKIPPED_ECOSYSTEMS = frozenset({"actions"}) +# Manifests of the tools CI installs, skipped for the same reason as the +# actions above: what they list runs on a CI runner and is never in anything +# this project ships. +# +# Named one file at a time rather than as the pip ecosystem, so a Python +# dependency of anything shipped would still be asked. The semgrep requirements +# went from one line to every package a clean install brings on 2026-10-14, so +# that vulnerability scanners read what CI really installs, and that put 65 new +# names in front of this gate - four of them with a licence GitHub leaves empty +# or describes with an identifier of its own. Vulnerabilities in this file are +# still checked, by the dependency review action and by OSV-Scanner. +SKIPPED_MANIFESTS = frozenset({".github/requirements-semgrep.txt"}) + def added(review): return [d for d in review if str(d.get("change_type", "")) == "added" - and str(d.get("ecosystem", "")).lower() not in SKIPPED_ECOSYSTEMS] + and str(d.get("ecosystem", "")).lower() not in SKIPPED_ECOSYSTEMS + and str(d.get("manifest", "")) not in SKIPPED_MANIFESTS] def parts_of(expression): diff --git a/.github/workflows/osv-pull-request.yml b/.github/workflows/osv-pull-request.yml new file mode 100644 index 00000000..146f5603 --- /dev/null +++ b/.github/workflows/osv-pull-request.yml @@ -0,0 +1,131 @@ +# Blocks a pull request that brings in a vulnerability the main branch does not +# already have, by any package manifest in the tree - go.mod and the pinned +# Python packages the semgrep job installs. +# +# The owner's decision of 2026-10-14: a pull request is stopped only for what it +# adds. What main already carries is reported once a week to the Security tab by +# osv-weekly.yml, and does not hold up a change that did not cause it. +# +# How it tells the two apart. The tree is scanned twice, first as the branch it +# merges into and then as the merge itself, and the reporter compares the two +# results - the method of the action's own pull request workflow. That workflow +# is not called as it is, because it keeps the job token in the checkout to +# switch branches and asks for security-events: write to upload. Neither is +# needed: the base is the first parent of the merge commit, which a fetch depth +# of two already holds, and a pull request has nothing to upload. +# +# Next to the two gates that already read the same go.mod. "review new +# dependencies" looks only at what the diff adds and only through GitHub's +# advisories. "known vulnerabilities" is govulncheck, which counts only what our +# code can reach. This one reads OSV, which also covers the Python packages, and +# counts a vulnerable package whether or not anything calls it - see +# --no-call-analysis below. +name: OSV pull request + +on: + pull_request: + +permissions: + contents: read + +# A push to the pull request makes the run before it pointless, and each run +# scans the tree twice. The same grouping as dev-build.yml. +concurrency: + group: osv-pull-request-${{ github.ref }} + cancel-in-progress: true + +jobs: + scan: + name: new vulnerabilities (OSV) + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + # The merge commit and its two parents, the first of which is the + # branch this pull request merges into. + fetch-depth: 2 + persist-credentials: false + + # Asserted rather than assumed, from the review of #181: with a commit + # of one parent here, HEAD^1 would be the pull request's own previous + # commit, and the comparison would find nothing new for the wrong reason. + - name: the branch this merges into + run: | + parents=$(git rev-list --parents -n 1 HEAD | wc -w) + if [ "$parents" -ne 3 ]; then + echo "::error::The checkout is not the merge commit of this pull request ($((parents - 1)) parent(s)), so there is no base to compare against." + exit 1 + fi + git checkout --quiet --detach HEAD^1 + + # The scanner exits 1 when it finds something and with another code when + # it cannot scan at all, and the step cannot tell the two apart. The + # results file can, and the check further down reads it. + # + # --no-call-analysis=go because it cannot work here and fails loudly + # when tried. It compiles the tree, and this tree refuses to compile + # without the tags in .github/build-tags and a C compiler with the + # headers of the window's toolkit, neither of which the scanner's image + # carries. Measured 2026-10-14: hundreds of compile errors in the log, and + # every Go advisory reported as if called anyway. Whether a vulnerable + # function is reachable is what the govulncheck job answers. + - name: scan the branch this merges into + id: old + uses: google/osv-scanner-action/osv-scanner-action@a345acffa64b0eaede81a3d9aae6141214d9c8fc # v2.6.0 + continue-on-error: true + with: + scan-args: |- + --format=json + --output=osv-old.json + --no-call-analysis=go + --recursive + ./ + + - name: the merge of this pull request + env: + MERGE: ${{ github.sha }} + run: git checkout --quiet --force --detach "$MERGE" + + - name: scan the merge of this pull request + id: new + uses: google/osv-scanner-action/osv-scanner-action@a345acffa64b0eaede81a3d9aae6141214d9c8fc # v2.6.0 + continue-on-error: true + with: + scan-args: |- + --format=json + --output=osv-new.json + --no-call-analysis=go + --recursive + ./ + + # Both files, whatever the scan steps returned. The reporter reads a file + # it cannot parse as one with no findings - measured 2026-10-14 on + # v2.6.0, from the review of #181: thirty findings cut short came out + # "No issues found", exit 0, and so did a missing file. Only a whole + # result goes on to be compared. + - name: both scans wrote a whole result + run: | + status=0 + for f in osv-old.json osv-new.json; do + if ! jq -e '.results | type == "array"' "$f" > /dev/null 2>&1; then + echo "::error::$f is missing or is not a whole scan result, so the scan did not complete. Read the scan steps above for the cause." + status=1 + fi + done + exit "$status" + + # Fails the job on anything in the second scan that is not in the first. + # The log of this step is the list: every finding with its package, the + # advisory and the version that fixes it. The annotations on the pull + # request are not - measured 2026-10-14 with findings in two files, one + # annotation came out, for one of them. + - name: what this pull request adds + uses: google/osv-scanner-action/osv-reporter-action@a345acffa64b0eaede81a3d9aae6141214d9c8fc # v2.6.0 + with: + scan-args: |- + --old=osv-old.json + --new=osv-new.json + --output=osv-new.sarif + --gh-annotations=true + --fail-on-vuln=true diff --git a/.github/workflows/osv-weekly.yml b/.github/workflows/osv-weekly.yml new file mode 100644 index 00000000..97552fb9 --- /dev/null +++ b/.github/workflows/osv-weekly.yml @@ -0,0 +1,99 @@ +# Once a week, every known vulnerability in what the main branch depends on, +# into the Security tab of the repository. +# +# The other half of osv-pull-request.yml. A pull request is stopped only for +# what it adds, by the owner's decision of 2026-10-14, so a vulnerability that +# was published against something main already uses has to show up somewhere +# else - and that place is here. Weekly because an advisory is published on +# its own schedule, not on ours: a tree nobody touches for a month still gets +# new findings. +# +# The run is red while anything is found, and the results go up either way. A +# clean upload is what closes the alerts of the week before, so an upload that +# happened only on findings would leave fixed alerts open for ever. Red so that +# a finding is also a failed run with a notice, rather than a line in a tab +# somebody has to remember to open. +# +# The same scanner and the same arguments as the pull request workflow, so the +# two cannot come to disagree about what counts. The reasons for each argument +# are written there. +name: OSV weekly + +on: + schedule: + # Monday 05:17 UTC, an hour after the weekly fuzzing in ci.yml. + - cron: "17 5 * * 1" + # By hand, from the Actions tab, after a fix is merged and before the next + # Monday - the alerts it closes close on the next upload. + workflow_dispatch: + +# A run by hand while the Monday one is going waits for it rather than +# uploading beside it. Not cancelled: each run is a whole scan worth keeping. +concurrency: + group: osv-weekly + cancel-in-progress: false + +permissions: + contents: read + +jobs: + scan: + name: vulnerabilities in main (OSV) + # By hand from another branch it would scan and upload that branch, and + # this workflow is about main. From the review of #181. + if: github.ref == format('refs/heads/{0}', github.event.repository.default_branch) + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: read + # The upload to the Security tab. Granted to this job only and to nothing + # a pull request runs. + security-events: write + # Read by the upload to name the run it belongs to - the action's own + # workflow grants it for the same step (github/codeql-action issue 2117). + actions: read + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: scan + id: scan + uses: google/osv-scanner-action/osv-scanner-action@a345acffa64b0eaede81a3d9aae6141214d9c8fc # v2.6.0 + continue-on-error: true + with: + scan-args: |- + --format=json + --output=osv-results.json + --no-call-analysis=go + --recursive + ./ + + # Whatever the scan step returned. The reporter reads a file it cannot + # parse as one with no findings and writes a clean SARIF from it - + # measured 2026-10-14 on v2.6.0 - and a clean upload closes every alert. + - name: the scan wrote a whole result + run: | + if ! jq -e '.results | type == "array"' osv-results.json > /dev/null 2>&1; then + echo "::error::osv-results.json is missing or is not a whole scan result, so the scan did not complete and nothing is uploaded. Read the scan step above for the cause." + exit 1 + fi + + # Writes the results for the Security tab and fails the job when there + # is any, which is after the file is written - the upload below runs + # either way. + - name: results for the Security tab + uses: google/osv-scanner-action/osv-reporter-action@a345acffa64b0eaede81a3d9aae6141214d9c8fc # v2.6.0 + with: + scan-args: |- + --new=osv-results.json + --output=osv-results.sarif + --gh-annotations=false + --fail-on-vuln=true + + - name: upload to the Security tab + if: ${{ !cancelled() && hashFiles('osv-results.sarif') != '' }} + uses: github/codeql-action/upload-sarif@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2 + with: + sarif_file: osv-results.sarif + category: osv-scanner diff --git a/CHANGELOG.md b/CHANGELOG.md index d5364767..5e9ba811 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -273,6 +273,11 @@ because it turns other people's test suites red. - **The window groups the settings of a format** when there are enough of them to need it. PDF shows its document properties under a heading of their own, and `tfg formats pdf` prints the same heading in the same place. +- **A GitHub button beside Donate.** It opens the project's page in your + browser - the source code, the releases and the place to report a + problem. It stands on every screen Donate stands on, and About has it in a + card of its own with the address written out, for a desktop that opens + nothing. ### Changed @@ -354,6 +359,11 @@ because it turns other people's test suites red. that the desktop window could reach through its toolkit. The command line tool does not use the network and was not affected. Building from source now needs Go 1.27.2. +- **The window is built with Fyne 2.8.2 and `golang.org/x/net` 0.60.0.** The + version of `golang.org/x/net` before had five published vulnerabilities. + The window uses the package only to read the SVG pictures it draws, none + of the five is in code it reaches, and the command line tool does not + contain the package at all. No generated file changes. ## [0.4.0] - 2026-09-25 diff --git a/SECURITY.md b/SECURITY.md index c9cabb23..03854184 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -101,10 +101,12 @@ could post a summary comment on a pull request, which would need `pull-requests: write` - that scope is deliberately not granted, because a failing check already says what the comment would. -The one workflow holding more is the one that publishes the website: `pages: write` +Two workflows hold more. The one that publishes the website has `pages: write` and `id-token: write`. It has a single job, and it runs only for a push to this repository - a condition added on 2026-08-27 and held by a guard, because the event -it triggers on fires with this repository's permissions. +it triggers on fires with this repository's permissions. The weekly vulnerability +scan has `security-events: write`, on its one job, to put its results in the +Security tab. It runs on a schedule or by hand, never for a pull request. **Every action is pinned to a commit** rather than to a tag somebody else can repoint, and a guard refuses any workflow that names one another way. @@ -112,7 +114,13 @@ repoint, and a guard refuses any workflow that names one another way. **Dependencies are checked on every pull request**, for known vulnerabilities and for licences. A licence GitHub cannot identify blocks the pull request, because for a GPL-3.0 project that ships a binary an unidentified licence is the one answer -nobody can act on. +nobody can act on. A pull request is also blocked when OSV-Scanner finds a known +vulnerability in it that the main branch does not have, in Go modules or in the +Python packages CI installs. + +**The main branch is scanned every week** by OSV-Scanner, and what it finds is +listed in the repository's Security tab. A vulnerability published against +something already in use shows up there, even when no pull request touches it. ## Code of conduct diff --git a/THIRD-PARTY-NOTICES.md b/THIRD-PARTY-NOTICES.md index e2ab0c8d..1a317c46 100644 --- a/THIRD-PARTY-NOTICES.md +++ b/THIRD-PARTY-NOTICES.md @@ -224,6 +224,15 @@ BSD 3-Clause and systray is still Apache-2.0, byte for byte the same file it shipped before. One module fewer is why the count above went down by one, and the MIT line with it. +The toolkit moved to v2.8.2 on 2026-10-14 and took three modules up with it - +`github.com/fyne-io/oksvg`, `github.com/go-gl/glfw/v3.4/glfw` and +`github.com/go-text/typesetting`. `golang.org/x/net` went to v0.60.0 on the +same day on its own, because five published advisories name v0.57.0. Every +licence file of the five, and the licence of the C library inside the glfw +module, was compared with the one at the version before and is byte for byte +the same. No module entered or left the graph, so the counts above did not +move. + Two arrived later than the rest. `github.com/FyshOS/fancyfs` came in on the same day with the folder picker, because the toolkit's dialog package imports it to decorate folder icons. Its licence was read before it was @@ -252,7 +261,7 @@ is the module's own MIT, carried unchanged. | module | version | licence | copyright | |---|---|---|---| -| `fyne.io/fyne/v2` | v2.8.1 | BSD-3-Clause | (C) 2018 Fyne.io developers (see AUTHORS) | +| `fyne.io/fyne/v2` | v2.8.2 | BSD-3-Clause | (C) 2018 Fyne.io developers (see AUTHORS) | | `fyne.io/systray` | v1.12.3-0.20260810170012-af4e8e793ec4 | Apache-2.0 | 2014 Brave New Software Project, Inc. | | `github.com/BurntSushi/toml` | v1.6.0 | MIT | (c) 2013 TOML authors | | `github.com/FyshOS/fancyfs` | v0.0.1 | BSD-3-Clause | (C) 2025 FyshOS developers (see AUTHORS) | @@ -260,11 +269,11 @@ is the module's own MIT, carried unchanged. | `github.com/clipperhouse/uax29/v2` | v2.2.0 | MIT | (c) 2020 Matt Sherman | | `github.com/fsnotify/fsnotify` | v1.9.0 | BSD-3-Clause | Copyright 2012 The Go Authors | | `github.com/fyne-io/image` | v0.1.1 | BSD-3-Clause | (c) 2022, Fyne.io | -| `github.com/fyne-io/oksvg` | v0.2.0 | BSD-3-Clause | (c) 2018, Steven R Wiley | +| `github.com/fyne-io/oksvg` | v0.2.1-0.20260918172519-a9af55fa95d9 | BSD-3-Clause | (c) 2018, Steven R Wiley | | `github.com/go-gl/gl` | v0.0.0-20260331235117-4566fea9a276 | MIT | (c) 2014 Eric Woroshow | -| `github.com/go-gl/glfw/v3.4/glfw` | v0.1.0-pre.1.0.20260707082822-2a407d02d01a | BSD-3-Clause | (c) 2012 The glfw3-go Authors. All rights reserved. | +| `github.com/go-gl/glfw/v3.4/glfw` | v0.1.0-pre.2 | BSD-3-Clause | (c) 2012 The glfw3-go Authors. All rights reserved. | | `github.com/go-text/render` | v0.2.1 | BSD-3-Clause | 2021 The go-text authors | -| `github.com/go-text/typesetting` | v0.3.4 | BSD-3-Clause | 2021 The go-text authors | +| `github.com/go-text/typesetting` | v0.3.5 | BSD-3-Clause | 2021 The go-text authors | | `github.com/godbus/dbus/v5` | v5.2.2 | BSD-2-Clause | (c) 2013, Georg Reinke (), Google | | `github.com/jeandeaual/go-locale` | v0.0.0-20250612000132-0ef82f21eade | MIT | (c) 2020 Alexis Jeandeau | | `github.com/jsummers/gobmp` | v0.0.0-20230614200233-a9de23ed2e25 | MIT | (c) 2012-2015 Jason Summers | @@ -276,7 +285,7 @@ is the module's own MIT, carried unchanged. | `github.com/srwiley/rasterx` | v0.0.0-20220730225603-2ab79fcdd4ef | BSD-3-Clause | (c) 2018, Steven R Wiley | | `github.com/yuin/goldmark` | v1.8.2 | MIT | (c) 2019 Yusuke Inuzuka | | `golang.org/x/image` | v0.46.0 | BSD-3-Clause | 2009 The Go Authors. | -| `golang.org/x/net` | v0.57.0 | BSD-3-Clause | 2009 The Go Authors. | +| `golang.org/x/net` | v0.60.0 | BSD-3-Clause | 2009 The Go Authors. | | `golang.org/x/sys` | v0.48.0 | BSD-3-Clause | 2009 The Go Authors. | The five licence texts follow, one copy each. They differ only in the copyright diff --git a/go.mod b/go.mod index 62c5fc68..86e0d028 100644 --- a/go.mod +++ b/go.mod @@ -77,7 +77,7 @@ go 1.27.2 // version is owed. require ( - fyne.io/fyne/v2 v2.8.1 + fyne.io/fyne/v2 v2.8.2 github.com/gen2brain/gav1d v0.2.5 github.com/gen2brain/jxl v0.2.0 github.com/goccy/go-yaml v1.19.2 @@ -97,11 +97,11 @@ require ( github.com/fyne-io/gl-js v0.2.1-0.20260315212741-029c47fd27e8 // indirect github.com/fyne-io/glfw-js v0.4.0 // indirect github.com/fyne-io/image v0.1.1 // indirect - github.com/fyne-io/oksvg v0.2.0 // indirect + github.com/fyne-io/oksvg v0.2.1-0.20260918172519-a9af55fa95d9 // indirect github.com/go-gl/gl v0.0.0-20260331235117-4566fea9a276 // indirect - github.com/go-gl/glfw/v3.4/glfw v0.1.0-pre.1.0.20260707082822-2a407d02d01a // indirect + github.com/go-gl/glfw/v3.4/glfw v0.1.0-pre.2 // indirect github.com/go-text/render v0.2.1 // indirect - github.com/go-text/typesetting v0.3.4 // indirect + github.com/go-text/typesetting v0.3.5 // indirect github.com/godbus/dbus/v5 v5.2.2 // indirect github.com/hack-pad/go-indexeddb v0.3.2 // indirect github.com/hack-pad/safejs v0.1.0 // indirect @@ -116,7 +116,7 @@ require ( github.com/stretchr/testify v1.11.1 // indirect github.com/yuin/goldmark v1.8.2 // indirect golang.org/x/image v0.46.0 // indirect - golang.org/x/net v0.57.0 // indirect + golang.org/x/net v0.60.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index a3831697..835346f6 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -fyne.io/fyne/v2 v2.8.1 h1:EztGuE2W3Qhd0cWVmU+h5rkzNezUD1To6UqsoLQYUIM= -fyne.io/fyne/v2 v2.8.1/go.mod h1:kpeuFrClm0fiAgJYr2soTfwKMT5rzNcSKzmgGjxvHOY= +fyne.io/fyne/v2 v2.8.2 h1:gYNJj+ZEPd2wF3M8SlF5iL6febpZkZ1csheRFkuEfHU= +fyne.io/fyne/v2 v2.8.2/go.mod h1:eXHJ/D9Y58UcDw7SkguadCRI64M4flXyjoMn827Q5SI= fyne.io/systray v1.12.3-0.20260810170012-af4e8e793ec4 h1:149/+Wa5EsLLXfyj2pdTmvnQf2VIlgCIwSjcCTHYhIo= fyne.io/systray v1.12.3-0.20260810170012-af4e8e793ec4/go.mod h1:RVwqP9nYMo7h5zViCBHri2FgjXF7H2cub7MAq4NSoLs= github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk= @@ -20,20 +20,20 @@ github.com/fyne-io/glfw-js v0.4.0 h1:I9hREBeFyI10cNIqbMKYb1PRidyPDgwob8o2la9SfQo github.com/fyne-io/glfw-js v0.4.0/go.mod h1:SDchsFZh4n7nVuBoiowOhOgIBdz+qUQVeC1w9fe2yVU= github.com/fyne-io/image v0.1.1 h1:WH0z4H7qfvNUw5l4p3bC1q70sa5+YWVt6HCj7y4VNyA= github.com/fyne-io/image v0.1.1/go.mod h1:xrfYBh6yspc+KjkgdZU/ifUC9sPA5Iv7WYUBzQKK7JM= -github.com/fyne-io/oksvg v0.2.0 h1:mxcGU2dx6nwjJsSA9PCYZDuoAcsZ/OuJlvg/Q9Njfo8= -github.com/fyne-io/oksvg v0.2.0/go.mod h1:dJ9oEkPiWhnTFNCmRgEze+YNprJF7YRbpjgpWS4kzoI= +github.com/fyne-io/oksvg v0.2.1-0.20260918172519-a9af55fa95d9 h1:owsdpq6a2KhaAA/hoHGQmFM7kOgKo3srXZVGnbEUzr0= +github.com/fyne-io/oksvg v0.2.1-0.20260918172519-a9af55fa95d9/go.mod h1:dJ9oEkPiWhnTFNCmRgEze+YNprJF7YRbpjgpWS4kzoI= github.com/gen2brain/gav1d v0.2.5 h1:Zg5/DE1JBdf8kxZqIvNo2ZEizF9DVF9jdPTAKJQZbOY= github.com/gen2brain/gav1d v0.2.5/go.mod h1:ReFKjHq7pvRhEk+pgRPAo9gOlcrf51sdzVcAbbX7e0E= github.com/gen2brain/jxl v0.2.0 h1:cXSQnfbcC0JGFzaI344VzPMbe7mh6rYc0tjPcBUsE4U= github.com/gen2brain/jxl v0.2.0/go.mod h1:VjJHRai/8I8Es01mULqyiOihRBOjJr1Zo+NoN/MyIwo= -github.com/go-gl/glfw/v3.4/glfw v0.1.0-pre.1.0.20260707082822-2a407d02d01a h1:HWK0MBggT/T6YH7VffE10xBIhqeTq8JzIUPJXrRy87g= -github.com/go-gl/glfw/v3.4/glfw v0.1.0-pre.1.0.20260707082822-2a407d02d01a/go.mod h1:T5Dn0JwIJOX1euPZ/iT4tq6nFYtmukjcYa7937HuYK8= +github.com/go-gl/glfw/v3.4/glfw v0.1.0-pre.2 h1:ez76TQInhkDIMobWbOFcPJw6FeB7coXRAVZpKwF8NBg= +github.com/go-gl/glfw/v3.4/glfw v0.1.0-pre.2/go.mod h1:T5Dn0JwIJOX1euPZ/iT4tq6nFYtmukjcYa7937HuYK8= github.com/go-text/render v0.2.1 h1:qwHhxqGUjjg4L0XyJWj7M7bpY75NZM+kBpv2Yfw5mcg= github.com/go-text/render v0.2.1/go.mod h1:HCCAq8MUlm/WRcXshBb4K/n+IkjeXQ1c2Ba+yICSm0A= -github.com/go-text/typesetting v0.3.4 h1:YYurUOtEb9kGSOz4uE3k4OpBGsp1dDL8+fjCeaFamAU= -github.com/go-text/typesetting v0.3.4/go.mod h1:4qZCQphq4KSgGTAeI0uMEkVbROgfah8BuyF5LRYr7XY= -github.com/go-text/typesetting-utils v0.0.0-20260223113751-2d88ac90dae3 h1:drBZzMgdYPbmyXqOto4YhhJGrFIQCX94FpR4MzTCsos= -github.com/go-text/typesetting-utils v0.0.0-20260223113751-2d88ac90dae3/go.mod h1:3/62I4La/HBRX9TcTpBj4eipLiwzf+vhI+7whTc9V7o= +github.com/go-text/typesetting v0.3.5 h1:XZPUooClHY0Vf/rFyUyuPRNEkawARaFzLMQcXLSEyPk= +github.com/go-text/typesetting v0.3.5/go.mod h1:XZO1hD+nQVyvVa5IicQk7FsCa4PFQaJ2soWAP1f//68= +github.com/go-text/typesetting-utils v0.0.0-20260419141703-4ffe8874dabc h1:8FGo2It5K75XkavhTiCKExUfVaVDS1feBnLCru5qeoY= +github.com/go-text/typesetting-utils v0.0.0-20260419141703-4ffe8874dabc/go.mod h1:3/62I4La/HBRX9TcTpBj4eipLiwzf+vhI+7whTc9V7o= github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ= @@ -72,8 +72,8 @@ go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/image v0.46.0 h1:b1+oYj0Jbp6K5MDT4i4/eZpYlk3V8SJhhDKh6LBHAyQ= golang.org/x/image v0.46.0/go.mod h1:3B3W05VGVQyuXucLINLjXKrqISASfi4Xj+iCVkLMwew= -golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE= -golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU= +golang.org/x/net v0.60.0 h1:79p50tfZlm0J9YfoDsSi639qSXNGVwEzOPLCxM2FsYU= +golang.org/x/net v0.60.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg= golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo= golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og= golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI= diff --git a/internal/guard/dependencygate_test.go b/internal/guard/dependencygate_test.go index 1ecff151..d0b56ef2 100644 --- a/internal/guard/dependencygate_test.go +++ b/internal/guard/dependencygate_test.go @@ -121,6 +121,39 @@ func TestTheDependencyGateRefusesALicenceThisProjectCannotDistribute(t *testing. "name": "actions/checkout", "version": "v7", "license": nil}}, 0, }, + { + // Measured on #181, 2026-10-14: the toolkit is reported with its + // fonts' licence beside its own, and both fonts are in the notices. + "the toolkit with the licence of the fonts it carries", + []dep{{"change_type": "added", "ecosystem": "gomod", + "name": "fyne.io/fyne/v2", "version": "2.8.2", "license": "BSD-3-Clause AND OFL-1.1"}}, + 0, + }, + { + // Allowing the fonts' licence allows that identifier, not anything + // it stands beside. + "the fonts' licence beside one this project cannot distribute", + []dep{{"change_type": "added", "ecosystem": "gomod", + "name": "example.com/fonts", "version": "v1.0.0", "license": "OFL-1.1 AND GPL-2.0-only"}}, + 1, + }, + { + // Measured on #181, 2026-10-14: typing_extensions and + // importlib_metadata came back with no licence. The file lists what + // the semgrep job installs on a runner, which nothing ships. + "a package the semgrep job installs, with no licence reported", + []dep{{"change_type": "added", "ecosystem": "pip", "manifest": ".github/requirements-semgrep.txt", + "name": "typing_extensions", "version": "4.16.0", "license": nil}}, + 0, + }, + { + // And the skip is that file, not the ecosystem: the same package + // listed anywhere else is asked like any other. + "the same package from any other manifest", + []dep{{"change_type": "added", "ecosystem": "pip", "manifest": "requirements.txt", + "name": "typing_extensions", "version": "4.16.0", "license": nil}}, + 1, + }, { "a dependency being REMOVED, which creates no obligation", []dep{{"change_type": "removed", "ecosystem": "gomod", diff --git a/internal/guard/menushape_test.go b/internal/guard/menushape_test.go index f7f432e3..0688cf70 100644 --- a/internal/guard/menushape_test.go +++ b/internal/guard/menushape_test.go @@ -216,7 +216,7 @@ func typingBoxesOn(screen fyne.CanvasObject) []*parts.Entry { // room its text takes, rather than against arithmetic repeated here. That is // deliberate for two reasons. A guard that recomputed parts.menuWidth would // agree with a wrong answer. And the string being measured is one the toolkit -// puts in by itself - fyne v2.8.1 widget/select.go line 94 substitutes its +// puts in by itself - fyne v2.8.2 widget/select.go line 98 substitutes its // default placeholder while the renderer is made - so the only party that knows // what a menu will end up showing is the menu. func TestAMenuIsWideEnoughForTheWordsTheToolkitPutsInIt(t *testing.T) { diff --git a/internal/guard/railgrouping_test.go b/internal/guard/railgrouping_test.go index dac3f9a5..8c8a8014 100644 --- a/internal/guard/railgrouping_test.go +++ b/internal/guard/railgrouping_test.go @@ -5,16 +5,24 @@ import ( "fyne.io/fyne/v2" + "github.com/donislawdev/TestingFilesGenerator/internal/gui/parts" "github.com/donislawdev/TestingFilesGenerator/internal/gui/text" ) -// Donate and Add a batch do not read as a pair. +// Add a batch does not read as a pair with the button before it. // // They stand side by side at the left of the action bar in the same style, and -// they are the two least related controls in the window: one adds to the form -// in front of you and the other hands an address to your browser. Side by side -// in one style, proximity says they belong together - which is the only thing -// proximity ever says. +// they are the least related controls in the window: Add a batch adds to the +// form in front of you, and the buttons before it - Donate, and GitHub since +// 2026-10-14 - hand an address to your browser. Side by side in one style, +// proximity says they belong together - which is the only thing proximity +// ever says. +// +// Asked of whatever stands nearest before it, found by where it is drawn, and +// not of Donate by name. Until 2026-10-14 this measured from Donate, and when +// GitHub arrived between the two the gap it measured held a whole button - so +// it stayed green with the boundary deleted, asking a question the screen no +// longer posed. // // Answered with a boundary rather than by moving the button. Under the last // batch is where the button belongs by proximity, and a guard already keeps it @@ -26,31 +34,61 @@ import ( // the same kind of thing to the same form, they sit together on purpose, and // whatever gap they use is what "these belong together" looks like on this // screen. Anything claiming to separate has to be wider than that. -func TestDonateAndAddABatchAreNotDrawnAsAPair(t *testing.T) { +func TestAddABatchIsNotDrawnAsAPairWithTheButtonBeforeIt(t *testing.T) { content, w := screenInAWindow(t, text.TabRecipe()) settle(content, w) - donate := buttonNamed(content, text.ButtonDonate()) add := buttonNamed(content, text.ButtonAddBatch()) preview := buttonNamed(content, text.ButtonPreview()) generate := buttonNamed(content, text.ButtonGenerate()) for name, b := range map[string]fyne.CanvasObject{ - text.ButtonDonate(): donate, text.ButtonAddBatch(): add, - text.ButtonPreview(): preview, text.ButtonGenerate(): generate, + text.ButtonAddBatch(): add, text.ButtonPreview(): preview, text.ButtonGenerate(): generate, } { if b == nil { t.Fatalf("the batch screen has no %q button, so this guard read the wrong tree", name) } } - apart := horizontalGap(t, content, donate, add) + before, name := buttonJustBefore(content, add) + if before == nil { + t.Fatalf("nothing stands before %q in its row, so this guard measured nothing. The screen carries: %v", + text.ButtonAddBatch(), buttonNames(content)) + } + + apart := horizontalGap(t, content, before, add) together := horizontalGap(t, content, preview, generate) if apart <= together { - t.Errorf("Donate and %q are %.1f px apart and two buttons that do belong together are %.1f px apart."+ + t.Errorf("%q and %q are %.1f px apart and two buttons that do belong together are %.1f px apart."+ " Two unrelated controls drawn at the spacing of a pair read as a pair", - text.ButtonAddBatch(), apart, together) + name, text.ButtonAddBatch(), apart, together) + } +} + +// buttonJustBefore is the button drawn nearest to the left of this one in the +// same row, and its words. +func buttonJustBefore(screen fyne.CanvasObject, of *parts.Button) (*parts.Button, string) { + target, ok := objectBox(screen, of) + if !ok { + return nil, "" + } + var found *parts.Button + edge := float32(-1) + atAbsolute(screen, func(o fyne.CanvasObject, at fyne.Position) { + b, isButton := o.(*parts.Button) + if !isButton || b == of || !b.Visible() { + return + } + right := at.X + o.Size().Width + sameRow := at.Y < target.Y+target.Height && target.Y < at.Y+o.Size().Height + if sameRow && right <= target.X && right > edge { + found, edge = b, right + } + }) + if found == nil { + return nil, "" } + return found, found.Text } // horizontalGap is the empty space between the right edge of one thing and the diff --git a/internal/guard/reachability_test.go b/internal/guard/reachability_test.go index 20cfe525..a4b4683d 100644 --- a/internal/guard/reachability_test.go +++ b/internal/guard/reachability_test.go @@ -414,6 +414,56 @@ func TestTheDonateButtonOpensTheSupportPage(t *testing.T) { } } +// The GitHub button stands wherever Donate stands, and on every screen it opens +// the project's page and nothing else. +// +// Wherever Donate stands is the owner's decision of 2026-10-14, and it is the +// half that would go wrong quietly. The four bars are built by four screens, +// and one built its own way would carry Donate and not GitHub while a guard +// asking only the first screen stayed green. So every screen is asked, and the +// two counts have to match - the About card included, which is not a bar. +// +// The address is asserted for the reason the Donate guard above gives: a wrong +// one looks identical from the inside of this program. +func TestTheGitHubButtonStandsBesideDonateAndOpensTheProjectPage(t *testing.T) { + host := newFakeHost(t) + window.Open(host) + + pressed := 0 + for _, name := range allTabs() { + screen := tabNamed(t, host.content, name) + donates := buttonsNamed(screen, text.ButtonDonate()) + gitHubs := buttonsNamed(screen, text.ButtonGitHub()) + if len(gitHubs) != len(donates) { + t.Errorf("the %q screen has %d Donate button(s) and %d GitHub button(s), "+ + "and the two stand together. It carries: %v", + name, len(donates), len(gitHubs), buttonNames(screen)) + } + for _, b := range gitHubs { + if b.Disabled() { + t.Errorf("the GitHub button on the %q screen is disabled", name) + continue + } + before := host.openedCount + b.OnTapped() + pressed++ + if host.openedCount != before+1 { + t.Errorf("pressing GitHub on the %q screen asked the window to open %d addresses", + name, host.openedCount-before) + } + if host.opened != text.RepositoryURL { + t.Errorf("GitHub on the %q screen goes to %q and the project page is %q", + name, host.opened, text.RepositoryURL) + } + } + } + // Asserted rather than assumed: a lookup that stopped finding buttons would + // match zero against zero on every screen and report nothing. + if pressed == 0 { + t.Fatalf("no GitHub button was found on any of %v, so nothing was checked", allTabs()) + } +} + // The address the button carries is the one in the sponsor file GitHub reads. // // Two places name where the money goes: this program and .github/FUNDING.yml. diff --git a/internal/guard/supportaddress_test.go b/internal/guard/supportaddress_test.go index b1039f92..0e2c715d 100644 --- a/internal/guard/supportaddress_test.go +++ b/internal/guard/supportaddress_test.go @@ -59,3 +59,21 @@ func TestTheSupportAddressStandsOnItsOwnLine(t *testing.T) { t.Errorf("the About screen names %q only inside a longer line, so somebody copying it\n"+ "has to pick it out of a sentence. The screen says:\n%s", text.SupportURL, shown) } + +// The project's address is readable on the About screen too, on a line of its +// own, since the GitHub button arrived on 2026-10-14 - one decision with the +// two guards above, for the same reason: the button hands the address to the +// desktop, a desktop with no browser does nothing, and the window stays quiet +// about that because the address is written here. +func TestTheProjectAddressStandsOnItsOwnLine(t *testing.T) { + shown := textIn(window.About(newFakeHost(t))) + + for _, line := range strings.Split(shown, "\n") { + if strings.TrimSpace(line) == text.RepositoryURL { + return + } + } + t.Errorf("the About screen does not carry %q as a line of its own, so somebody whose\n"+ + "desktop cannot open the GitHub button has no address to type. The screen says:\n%s", + text.RepositoryURL, shown) +} diff --git a/internal/guard/testdata/screens/about.png b/internal/guard/testdata/screens/about.png index 0f6d9770..3fa8aa02 100644 Binary files a/internal/guard/testdata/screens/about.png and b/internal/guard/testdata/screens/about.png differ diff --git a/internal/guard/testdata/screens/about.xml b/internal/guard/testdata/screens/about.xml index ba6ccea5..a4d7b200 100644 --- a/internal/guard/testdata/screens/about.xml +++ b/internal/guard/testdata/screens/about.xml @@ -40,8 +40,8 @@ - - + + @@ -135,6 +135,37 @@ + + + + Source code + + + + + Opens the project page on GitHub in your browser - the source code, the releases and the place to report a problem. + + + + + + + + GitHub + + + + + + https://github.com/donislawdev/TestingFilesGenerator + + + + + + + + @@ -166,7 +197,7 @@ - + @@ -892,7 +923,7 @@ - + @@ -1089,7 +1120,7 @@ - + @@ -1133,8 +1164,8 @@ - - + + diff --git a/internal/guard/testdata/screens/generate-chosen-by-key.png b/internal/guard/testdata/screens/generate-chosen-by-key.png index 8c66b77f..e6300dc3 100644 Binary files a/internal/guard/testdata/screens/generate-chosen-by-key.png and b/internal/guard/testdata/screens/generate-chosen-by-key.png differ diff --git a/internal/guard/testdata/screens/generate-chosen-by-key.xml b/internal/guard/testdata/screens/generate-chosen-by-key.xml index 053b9f95..f8633248 100644 --- a/internal/guard/testdata/screens/generate-chosen-by-key.xml +++ b/internal/guard/testdata/screens/generate-chosen-by-key.xml @@ -480,6 +480,11 @@ Donate + + + + GitHub + diff --git a/internal/guard/testdata/screens/generate-chosen.png b/internal/guard/testdata/screens/generate-chosen.png index 1e2f9a66..bafdf931 100644 Binary files a/internal/guard/testdata/screens/generate-chosen.png and b/internal/guard/testdata/screens/generate-chosen.png differ diff --git a/internal/guard/testdata/screens/generate-chosen.xml b/internal/guard/testdata/screens/generate-chosen.xml index 32e866f7..e9e58f1f 100644 --- a/internal/guard/testdata/screens/generate-chosen.xml +++ b/internal/guard/testdata/screens/generate-chosen.xml @@ -480,6 +480,11 @@ Donate + + + + GitHub + diff --git a/internal/guard/testdata/screens/generate-empty.png b/internal/guard/testdata/screens/generate-empty.png index ab61291c..43315732 100644 Binary files a/internal/guard/testdata/screens/generate-empty.png and b/internal/guard/testdata/screens/generate-empty.png differ diff --git a/internal/guard/testdata/screens/generate-empty.xml b/internal/guard/testdata/screens/generate-empty.xml index 37f46f9b..5777b3b2 100644 --- a/internal/guard/testdata/screens/generate-empty.xml +++ b/internal/guard/testdata/screens/generate-empty.xml @@ -490,6 +490,11 @@ Donate + + + + GitHub + diff --git a/internal/guard/testdata/screens/generate-focused.png b/internal/guard/testdata/screens/generate-focused.png index 668a91b4..efd6aa9b 100644 Binary files a/internal/guard/testdata/screens/generate-focused.png and b/internal/guard/testdata/screens/generate-focused.png differ diff --git a/internal/guard/testdata/screens/generate-focused.xml b/internal/guard/testdata/screens/generate-focused.xml index 1c96076f..83d695ec 100644 --- a/internal/guard/testdata/screens/generate-focused.xml +++ b/internal/guard/testdata/screens/generate-focused.xml @@ -481,6 +481,11 @@ Donate + + + + GitHub + diff --git a/internal/guard/testdata/screens/generate-hovered.png b/internal/guard/testdata/screens/generate-hovered.png index 0b2410d6..004c32f9 100644 Binary files a/internal/guard/testdata/screens/generate-hovered.png and b/internal/guard/testdata/screens/generate-hovered.png differ diff --git a/internal/guard/testdata/screens/generate-hovered.xml b/internal/guard/testdata/screens/generate-hovered.xml index 91aa3555..2b1a3433 100644 --- a/internal/guard/testdata/screens/generate-hovered.xml +++ b/internal/guard/testdata/screens/generate-hovered.xml @@ -480,6 +480,11 @@ Donate + + + + GitHub + diff --git a/internal/guard/testdata/screens/generate-menu-hovered.png b/internal/guard/testdata/screens/generate-menu-hovered.png index 0c4d5caa..064811be 100644 Binary files a/internal/guard/testdata/screens/generate-menu-hovered.png and b/internal/guard/testdata/screens/generate-menu-hovered.png differ diff --git a/internal/guard/testdata/screens/generate-menu-hovered.xml b/internal/guard/testdata/screens/generate-menu-hovered.xml index 6e7975f1..7455a897 100644 --- a/internal/guard/testdata/screens/generate-menu-hovered.xml +++ b/internal/guard/testdata/screens/generate-menu-hovered.xml @@ -480,6 +480,11 @@ Donate + + + + GitHub + diff --git a/internal/guard/testdata/screens/generate-menu-keyed.png b/internal/guard/testdata/screens/generate-menu-keyed.png index 15d6dc79..e45f9608 100644 Binary files a/internal/guard/testdata/screens/generate-menu-keyed.png and b/internal/guard/testdata/screens/generate-menu-keyed.png differ diff --git a/internal/guard/testdata/screens/generate-menu-keyed.xml b/internal/guard/testdata/screens/generate-menu-keyed.xml index f7b8c69b..5653abe4 100644 --- a/internal/guard/testdata/screens/generate-menu-keyed.xml +++ b/internal/guard/testdata/screens/generate-menu-keyed.xml @@ -480,6 +480,11 @@ Donate + + + + GitHub + diff --git a/internal/guard/testdata/screens/generate-menu.png b/internal/guard/testdata/screens/generate-menu.png index 3657dc4d..be493ce4 100644 Binary files a/internal/guard/testdata/screens/generate-menu.png and b/internal/guard/testdata/screens/generate-menu.png differ diff --git a/internal/guard/testdata/screens/generate-menu.xml b/internal/guard/testdata/screens/generate-menu.xml index 7368046b..36185c86 100644 --- a/internal/guard/testdata/screens/generate-menu.xml +++ b/internal/guard/testdata/screens/generate-menu.xml @@ -480,6 +480,11 @@ Donate + + + + GitHub + diff --git a/internal/guard/testdata/screens/generate-pdf-settings.png b/internal/guard/testdata/screens/generate-pdf-settings.png index e5a24f5b..e3916b95 100644 Binary files a/internal/guard/testdata/screens/generate-pdf-settings.png and b/internal/guard/testdata/screens/generate-pdf-settings.png differ diff --git a/internal/guard/testdata/screens/generate-pdf-settings.xml b/internal/guard/testdata/screens/generate-pdf-settings.xml index 8579e561..8e83d18a 100644 --- a/internal/guard/testdata/screens/generate-pdf-settings.xml +++ b/internal/guard/testdata/screens/generate-pdf-settings.xml @@ -876,6 +876,11 @@ Donate + + + + GitHub + diff --git a/internal/guard/testdata/screens/generate-refused-both.png b/internal/guard/testdata/screens/generate-refused-both.png index be8c64a6..bb49e0d7 100644 Binary files a/internal/guard/testdata/screens/generate-refused-both.png and b/internal/guard/testdata/screens/generate-refused-both.png differ diff --git a/internal/guard/testdata/screens/generate-refused-both.xml b/internal/guard/testdata/screens/generate-refused-both.xml index a88a9fdf..b0f91d55 100644 --- a/internal/guard/testdata/screens/generate-refused-both.xml +++ b/internal/guard/testdata/screens/generate-refused-both.xml @@ -499,6 +499,11 @@ Donate + + + + GitHub + diff --git a/internal/guard/testdata/screens/generate-refused-setting.png b/internal/guard/testdata/screens/generate-refused-setting.png index f4883410..71a00c70 100644 Binary files a/internal/guard/testdata/screens/generate-refused-setting.png and b/internal/guard/testdata/screens/generate-refused-setting.png differ diff --git a/internal/guard/testdata/screens/generate-refused-setting.xml b/internal/guard/testdata/screens/generate-refused-setting.xml index 91c6a2cc..aa8c1569 100644 --- a/internal/guard/testdata/screens/generate-refused-setting.xml +++ b/internal/guard/testdata/screens/generate-refused-setting.xml @@ -558,6 +558,11 @@ Donate + + + + GitHub + diff --git a/internal/guard/testdata/screens/generate-refused.png b/internal/guard/testdata/screens/generate-refused.png index b00cb420..d9462bd7 100644 Binary files a/internal/guard/testdata/screens/generate-refused.png and b/internal/guard/testdata/screens/generate-refused.png differ diff --git a/internal/guard/testdata/screens/generate-refused.xml b/internal/guard/testdata/screens/generate-refused.xml index e5d63d64..87685aab 100644 --- a/internal/guard/testdata/screens/generate-refused.xml +++ b/internal/guard/testdata/screens/generate-refused.xml @@ -499,6 +499,11 @@ Donate + + + + GitHub + diff --git a/internal/guard/testdata/screens/generate-switch-by-key.png b/internal/guard/testdata/screens/generate-switch-by-key.png index 95a49490..55c1f400 100644 Binary files a/internal/guard/testdata/screens/generate-switch-by-key.png and b/internal/guard/testdata/screens/generate-switch-by-key.png differ diff --git a/internal/guard/testdata/screens/generate-switch-by-key.xml b/internal/guard/testdata/screens/generate-switch-by-key.xml index e70c8aa1..9d56a829 100644 --- a/internal/guard/testdata/screens/generate-switch-by-key.xml +++ b/internal/guard/testdata/screens/generate-switch-by-key.xml @@ -480,6 +480,11 @@ Donate + + + + GitHub + diff --git a/internal/guard/testdata/screens/generate-typed.png b/internal/guard/testdata/screens/generate-typed.png index e9f041c1..59483740 100644 Binary files a/internal/guard/testdata/screens/generate-typed.png and b/internal/guard/testdata/screens/generate-typed.png differ diff --git a/internal/guard/testdata/screens/generate-typed.xml b/internal/guard/testdata/screens/generate-typed.xml index 3a5fb2ee..06ed6e85 100644 --- a/internal/guard/testdata/screens/generate-typed.xml +++ b/internal/guard/testdata/screens/generate-typed.xml @@ -489,6 +489,11 @@ Donate + + + + GitHub + diff --git a/internal/guard/testdata/screens/generate-unchecked.png b/internal/guard/testdata/screens/generate-unchecked.png index bfd063ce..90a40bf0 100644 Binary files a/internal/guard/testdata/screens/generate-unchecked.png and b/internal/guard/testdata/screens/generate-unchecked.png differ diff --git a/internal/guard/testdata/screens/generate-unchecked.xml b/internal/guard/testdata/screens/generate-unchecked.xml index 869b133f..cf7beb02 100644 --- a/internal/guard/testdata/screens/generate-unchecked.xml +++ b/internal/guard/testdata/screens/generate-unchecked.xml @@ -479,6 +479,11 @@ Donate + + + + GitHub + diff --git a/internal/guard/testdata/screens/generate.png b/internal/guard/testdata/screens/generate.png index 36d218a6..1543a3b1 100644 Binary files a/internal/guard/testdata/screens/generate.png and b/internal/guard/testdata/screens/generate.png differ diff --git a/internal/guard/testdata/screens/generate.xml b/internal/guard/testdata/screens/generate.xml index 0baaf856..8e4a8029 100644 --- a/internal/guard/testdata/screens/generate.xml +++ b/internal/guard/testdata/screens/generate.xml @@ -480,6 +480,11 @@ Donate + + + + GitHub + diff --git a/internal/guard/testdata/screens/preset-many-settings.png b/internal/guard/testdata/screens/preset-many-settings.png index 42242b6c..154a3007 100644 Binary files a/internal/guard/testdata/screens/preset-many-settings.png and b/internal/guard/testdata/screens/preset-many-settings.png differ diff --git a/internal/guard/testdata/screens/preset-many-settings.xml b/internal/guard/testdata/screens/preset-many-settings.xml index 2a0d389b..7126efe6 100644 --- a/internal/guard/testdata/screens/preset-many-settings.xml +++ b/internal/guard/testdata/screens/preset-many-settings.xml @@ -534,6 +534,11 @@ Donate + + + + GitHub + diff --git a/internal/guard/testdata/screens/preset-menu-setting.png b/internal/guard/testdata/screens/preset-menu-setting.png index 83aa78c9..044ac061 100644 Binary files a/internal/guard/testdata/screens/preset-menu-setting.png and b/internal/guard/testdata/screens/preset-menu-setting.png differ diff --git a/internal/guard/testdata/screens/preset-menu-setting.xml b/internal/guard/testdata/screens/preset-menu-setting.xml index 88f2e799..762847fe 100644 --- a/internal/guard/testdata/screens/preset-menu-setting.xml +++ b/internal/guard/testdata/screens/preset-menu-setting.xml @@ -443,6 +443,11 @@ Donate + + + + GitHub + diff --git a/internal/guard/testdata/screens/preset-menu.png b/internal/guard/testdata/screens/preset-menu.png index 96e95fed..6e4af030 100644 Binary files a/internal/guard/testdata/screens/preset-menu.png and b/internal/guard/testdata/screens/preset-menu.png differ diff --git a/internal/guard/testdata/screens/preset-menu.xml b/internal/guard/testdata/screens/preset-menu.xml index c0643ad0..333f3b9b 100644 --- a/internal/guard/testdata/screens/preset-menu.xml +++ b/internal/guard/testdata/screens/preset-menu.xml @@ -387,6 +387,11 @@ Donate + + + + GitHub + diff --git a/internal/guard/testdata/screens/preset-refused.png b/internal/guard/testdata/screens/preset-refused.png index e28da35b..39ab1f3b 100644 Binary files a/internal/guard/testdata/screens/preset-refused.png and b/internal/guard/testdata/screens/preset-refused.png differ diff --git a/internal/guard/testdata/screens/preset-refused.xml b/internal/guard/testdata/screens/preset-refused.xml index 581be9db..1ed0ed74 100644 --- a/internal/guard/testdata/screens/preset-refused.xml +++ b/internal/guard/testdata/screens/preset-refused.xml @@ -452,6 +452,11 @@ Donate + + + + GitHub + diff --git a/internal/guard/testdata/screens/preset.png b/internal/guard/testdata/screens/preset.png index 7cbb12d2..f98ab3b5 100644 Binary files a/internal/guard/testdata/screens/preset.png and b/internal/guard/testdata/screens/preset.png differ diff --git a/internal/guard/testdata/screens/preset.xml b/internal/guard/testdata/screens/preset.xml index 0d6ed30c..fd12d325 100644 --- a/internal/guard/testdata/screens/preset.xml +++ b/internal/guard/testdata/screens/preset.xml @@ -387,6 +387,11 @@ Donate + + + + GitHub + diff --git a/internal/guard/testdata/screens/recipe-contents.png b/internal/guard/testdata/screens/recipe-contents.png index 6d11ba59..253b9c6e 100644 Binary files a/internal/guard/testdata/screens/recipe-contents.png and b/internal/guard/testdata/screens/recipe-contents.png differ diff --git a/internal/guard/testdata/screens/recipe-contents.xml b/internal/guard/testdata/screens/recipe-contents.xml index 6b0392a4..8e595413 100644 --- a/internal/guard/testdata/screens/recipe-contents.xml +++ b/internal/guard/testdata/screens/recipe-contents.xml @@ -671,12 +671,12 @@ - + Preview - + Generate @@ -705,9 +705,14 @@ Donate - + + + + GitHub + + - + Add a batch diff --git a/internal/guard/testdata/screens/recipe-on-a-preset.png b/internal/guard/testdata/screens/recipe-on-a-preset.png index d209116c..236859ac 100644 Binary files a/internal/guard/testdata/screens/recipe-on-a-preset.png and b/internal/guard/testdata/screens/recipe-on-a-preset.png differ diff --git a/internal/guard/testdata/screens/recipe-on-a-preset.xml b/internal/guard/testdata/screens/recipe-on-a-preset.xml index bdbf6888..ad66e9fe 100644 --- a/internal/guard/testdata/screens/recipe-on-a-preset.xml +++ b/internal/guard/testdata/screens/recipe-on-a-preset.xml @@ -641,12 +641,12 @@ - + Preview - + Generate @@ -675,9 +675,14 @@ Donate - + + + + GitHub + + - + Add a batch diff --git a/internal/guard/testdata/screens/recipe-refused-with-one-batch-filled.png b/internal/guard/testdata/screens/recipe-refused-with-one-batch-filled.png index 21346917..276401df 100644 Binary files a/internal/guard/testdata/screens/recipe-refused-with-one-batch-filled.png and b/internal/guard/testdata/screens/recipe-refused-with-one-batch-filled.png differ diff --git a/internal/guard/testdata/screens/recipe-refused-with-one-batch-filled.xml b/internal/guard/testdata/screens/recipe-refused-with-one-batch-filled.xml index a43031f8..3d7b2daa 100644 --- a/internal/guard/testdata/screens/recipe-refused-with-one-batch-filled.xml +++ b/internal/guard/testdata/screens/recipe-refused-with-one-batch-filled.xml @@ -905,12 +905,12 @@ - + Preview - + Generate @@ -939,9 +939,14 @@ Donate - + + + + GitHub + + - + Add a batch diff --git a/internal/guard/testdata/screens/recipe-refused.png b/internal/guard/testdata/screens/recipe-refused.png index ed38575b..d41c3a9d 100644 Binary files a/internal/guard/testdata/screens/recipe-refused.png and b/internal/guard/testdata/screens/recipe-refused.png differ diff --git a/internal/guard/testdata/screens/recipe-refused.xml b/internal/guard/testdata/screens/recipe-refused.xml index 7aac111d..5ce49eb1 100644 --- a/internal/guard/testdata/screens/recipe-refused.xml +++ b/internal/guard/testdata/screens/recipe-refused.xml @@ -606,12 +606,12 @@ - + Preview - + Generate @@ -640,9 +640,14 @@ Donate - + + + + GitHub + + - + Add a batch diff --git a/internal/guard/testdata/screens/recipe-two-batches.png b/internal/guard/testdata/screens/recipe-two-batches.png index d8339a2b..9ce57a99 100644 Binary files a/internal/guard/testdata/screens/recipe-two-batches.png and b/internal/guard/testdata/screens/recipe-two-batches.png differ diff --git a/internal/guard/testdata/screens/recipe-two-batches.xml b/internal/guard/testdata/screens/recipe-two-batches.xml index 25a56a74..6fdeca9b 100644 --- a/internal/guard/testdata/screens/recipe-two-batches.xml +++ b/internal/guard/testdata/screens/recipe-two-batches.xml @@ -882,12 +882,12 @@ - + Preview - + Generate @@ -916,9 +916,14 @@ Donate - + + + + GitHub + + - + Add a batch diff --git a/internal/guard/testdata/screens/recipe.png b/internal/guard/testdata/screens/recipe.png index 6b481207..681e5b15 100644 Binary files a/internal/guard/testdata/screens/recipe.png and b/internal/guard/testdata/screens/recipe.png differ diff --git a/internal/guard/testdata/screens/recipe.xml b/internal/guard/testdata/screens/recipe.xml index 6aabe93e..cc41c0c6 100644 --- a/internal/guard/testdata/screens/recipe.xml +++ b/internal/guard/testdata/screens/recipe.xml @@ -577,12 +577,12 @@ - + Preview - + Generate @@ -611,9 +611,14 @@ Donate - + + + + GitHub + + - + Add a batch diff --git a/internal/guard/testdata/screens/tools-check-result.png b/internal/guard/testdata/screens/tools-check-result.png index 1563e6f1..0e82cd8d 100644 Binary files a/internal/guard/testdata/screens/tools-check-result.png and b/internal/guard/testdata/screens/tools-check-result.png differ diff --git a/internal/guard/testdata/screens/tools-check-result.xml b/internal/guard/testdata/screens/tools-check-result.xml index 3bc8b2f7..edc3b73c 100644 --- a/internal/guard/testdata/screens/tools-check-result.xml +++ b/internal/guard/testdata/screens/tools-check-result.xml @@ -294,6 +294,11 @@ Donate + + + + GitHub + diff --git a/internal/guard/testdata/screens/tools-refused.png b/internal/guard/testdata/screens/tools-refused.png index 386f4506..4eb9e09c 100644 Binary files a/internal/guard/testdata/screens/tools-refused.png and b/internal/guard/testdata/screens/tools-refused.png differ diff --git a/internal/guard/testdata/screens/tools-refused.xml b/internal/guard/testdata/screens/tools-refused.xml index 263ae7ec..24c79931 100644 --- a/internal/guard/testdata/screens/tools-refused.xml +++ b/internal/guard/testdata/screens/tools-refused.xml @@ -347,6 +347,11 @@ Donate + + + + GitHub + diff --git a/internal/guard/testdata/screens/tools-result.png b/internal/guard/testdata/screens/tools-result.png index 404c64c1..9616cc6e 100644 Binary files a/internal/guard/testdata/screens/tools-result.png and b/internal/guard/testdata/screens/tools-result.png differ diff --git a/internal/guard/testdata/screens/tools-result.xml b/internal/guard/testdata/screens/tools-result.xml index e99eb8d1..363a8c42 100644 --- a/internal/guard/testdata/screens/tools-result.xml +++ b/internal/guard/testdata/screens/tools-result.xml @@ -371,6 +371,11 @@ Donate + + + + GitHub + diff --git a/internal/guard/testdata/screens/tools-write.png b/internal/guard/testdata/screens/tools-write.png index af09e6dc..85bee5da 100644 Binary files a/internal/guard/testdata/screens/tools-write.png and b/internal/guard/testdata/screens/tools-write.png differ diff --git a/internal/guard/testdata/screens/tools-write.xml b/internal/guard/testdata/screens/tools-write.xml index d83d29dc..5624311a 100644 --- a/internal/guard/testdata/screens/tools-write.xml +++ b/internal/guard/testdata/screens/tools-write.xml @@ -308,6 +308,11 @@ Donate + + + + GitHub + diff --git a/internal/guard/testdata/screens/tools.png b/internal/guard/testdata/screens/tools.png index 185bb835..2c51fcfc 100644 Binary files a/internal/guard/testdata/screens/tools.png and b/internal/guard/testdata/screens/tools.png differ diff --git a/internal/guard/testdata/screens/tools.xml b/internal/guard/testdata/screens/tools.xml index f2cf37d7..7e57fbc3 100644 --- a/internal/guard/testdata/screens/tools.xml +++ b/internal/guard/testdata/screens/tools.xml @@ -343,6 +343,11 @@ Donate + + + + GitHub + diff --git a/internal/gui/parts/menulook.go b/internal/gui/parts/menulook.go index 67e9807e..cf0a20b0 100644 --- a/internal/gui/parts/menulook.go +++ b/internal/gui/parts/menulook.go @@ -112,7 +112,7 @@ func (m *menuLook) showKind() { // placeKind stands the picture where the toolkit starts the value's words and // moves the words along by the picture and a gap. The words are found by type, // the way accent finds the arrow: the toolkit's renderer holds exactly one -// RichText (fyne v2.8.1 widget/select.go, CreateRenderer). +// RichText (fyne v2.8.2 widget/select.go, CreateRenderer). // // Placed by the toolkit's own arithmetic rather than by moving whatever is // there, so running it twice gives the same picture: the words start at the diff --git a/internal/gui/parts/ring.go b/internal/gui/parts/ring.go index 8db16547..2ba3cb62 100644 --- a/internal/gui/parts/ring.go +++ b/internal/gui/parts/ring.go @@ -316,7 +316,7 @@ func menuWidth(c *Chooser) float32 { th, size := Theme(), theme.TextSize() // The VALUES, and not the placeholder. The toolkit puts its own default // placeholder in when the field is empty and does it while the renderer is - // made - fyne v2.8.1 widget/select.go line 94, read in the pinned module - + // made - fyne v2.8.2 widget/select.go line 98, read in the pinned module - // so a menu measured before it has been drawn has no placeholder and a // menu measured after it has been drawn has "(Select one)". Until // 2026-09-16 this line measured whatever was set, and the same menu came diff --git a/internal/gui/text/locale/en.json b/internal/gui/text/locale/en.json index c721910d..d718896d 100644 --- a/internal/gui/text/locale/en.json +++ b/internal/gui/text/locale/en.json @@ -124,6 +124,11 @@ "hash": "sha256-49e49bb4401e", "other": "Generate" }, + "ButtonGitHub": { + "description": "The words on a button.", + "hash": "sha256-f911e414cf6b", + "other": "GitHub" + }, "ButtonOpenFolder": { "description": "The words on a button.", "hash": "sha256-6a9084025c4b", @@ -224,6 +229,11 @@ "hash": "sha256-7923af561fdd", "other": "It reaches the manifest and nothing else reads it. Leave it alone where the right answer depends on the application's own policy." }, + "DetailGitHub": { + "description": "The longer explanation behind the button beside a field name.", + "hash": "sha256-e3cd538a1d71", + "other": "Opens the project page on GitHub in your browser - the source code, the releases and the place to report a problem." + }, "DetailGroup": { "description": "The longer explanation behind the button beside a field name.", "hash": "sha256-37e7791bbda7", @@ -831,6 +841,11 @@ "hash": "sha256-74a883a037bc", "other": "Settings" }, + "SectionSource": { + "description": "The heading over a group of fields.", + "hash": "sha256-bf046c7395cb", + "other": "Source code" + }, "SectionSupport": { "description": "The heading over a group of fields.", "hash": "sha256-be91940b79f4", diff --git a/internal/gui/text/locale/pl.json b/internal/gui/text/locale/pl.json index 20cac93e..40491977 100644 --- a/internal/gui/text/locale/pl.json +++ b/internal/gui/text/locale/pl.json @@ -24,6 +24,7 @@ "ButtonDuplicateBatch": { "hash": "sha256-02cdaabfca80", "other": "Powiel" }, "ButtonForget": { "hash": "sha256-a6bd489d9550", "other": "Zapomnij" }, "ButtonGenerate": { "hash": "sha256-49e49bb4401e", "other": "Generuj" }, + "ButtonGitHub": { "hash": "sha256-f911e414cf6b", "other": "GitHub" }, "ButtonOpenFolder": { "hash": "sha256-6a9084025c4b", "other": "Otwórz folder" }, "ButtonOpenInstructions": { "hash": "sha256-d5ca5d9e594b", "other": "Otwórz instrukcje" }, "ButtonOpenManifest": { "hash": "sha256-520b7632c1e9", "other": "Otwórz manifest" }, @@ -44,6 +45,7 @@ "DetailDamage": { "hash": "sha256-96921213f617", "other": "Pliki mają rozmiar, o który prosisz, i żaden czytnik ich nie przyjmie - a właśnie to walidator ma odrzucić. Manifest zapisuje, co zostało zepsute, i mówi, że plik powinien zostać odrzucony." }, "DetailDonate": { "hash": "sha256-8531eb297aaa", "other": "Otwiera stronę wsparcia w przeglądarce. Narzędzie jest darmowe i takie zostanie - to płaci za czas, który w nie idzie." }, "DetailExpected": { "hash": "sha256-7923af561fdd", "other": "Trafia do manifestu i nic innego go nie czyta. Zostaw puste tam, gdzie właściwa odpowiedź zależy od polityki samej aplikacji." }, + "DetailGitHub": { "hash": "sha256-e3cd538a1d71", "other": "Otwiera stronę projektu na GitHubie w przeglądarce - kod źródłowy, wydania i miejsce na zgłoszenie problemu." }, "DetailGroup": { "hash": "sha256-37e7791bbda7", "other": "Trafia do manifestu, więc test może sprawdzić całą klasę przypadków naraz." }, "DetailLabel": { "hash": "sha256-aeb24fe1804e", "other": "Zapisuje w pliku, czym jest i jaki miał mieć rozmiar. Wyłącz dla pliku, który ma zawierać wyłącznie swoją treść." }, "DetailManifest": { "hash": "sha256-98009d7dd5ba", "other": "Trafia do katalogu wyjściowego obok plików." }, @@ -165,6 +167,7 @@ "SectionPreset": { "hash": "sha256-ed65b5dde19f", "other": "Pytanie" }, "SectionRemembered": { "hash": "sha256-3969bfb60b8d", "other": "Co pamięta to okno" }, "SectionSettings": { "hash": "sha256-74a883a037bc", "other": "Ustawienia" }, + "SectionSource": { "hash": "sha256-bf046c7395cb", "other": "Kod źródłowy" }, "SectionSupport": { "hash": "sha256-be91940b79f4", "other": "Wsparcie" }, "SectionTool": { "hash": "sha256-ed65b5dde19f", "other": "Pytanie" }, "SectionToolInput": { "hash": "sha256-4f8655e48218", "other": "Na czym pracuje" }, diff --git a/internal/gui/text/locale/toolkit/en.json b/internal/gui/text/locale/toolkit/en.json index 1cd58598..ebc4504b 100644 --- a/internal/gui/text/locale/toolkit/en.json +++ b/internal/gui/text/locale/toolkit/en.json @@ -1,5 +1,6 @@ { "(Parent)": { "description": "A word of the graphics toolkit the window is built with - in its menus and dialogs. The window gives the toolkit these in the language it speaks.", "hash": "sha256-c81daefccf15", "other": "(Parent)" }, + "(Select one)": { "description": "A word of the graphics toolkit the window is built with - in its menus and dialogs. The window gives the toolkit these in the language it speaks.", "hash": "sha256-fdfe6bf17ea5", "other": "(Select one)" }, "Advanced": { "description": "A word of the graphics toolkit the window is built with - in its menus and dialogs. The window gives the toolkit these in the language it speaks.", "hash": "sha256-9f088dbebd6c", "other": "Advanced" }, "Cancel": { "description": "A word of the graphics toolkit the window is built with - in its menus and dialogs. The window gives the toolkit these in the language it speaks.", "hash": "sha256-19766ed6ccb2", "other": "Cancel" }, "Confirm": { "description": "A word of the graphics toolkit the window is built with - in its menus and dialogs. The window gives the toolkit these in the language it speaks.", "hash": "sha256-eebdd24a77d9", "other": "Confirm" }, diff --git a/internal/gui/text/locale/toolkit/pl.json b/internal/gui/text/locale/toolkit/pl.json index aa35af62..e5e00d7f 100644 --- a/internal/gui/text/locale/toolkit/pl.json +++ b/internal/gui/text/locale/toolkit/pl.json @@ -1,5 +1,6 @@ { "(Parent)": { "hash": "sha256-c81daefccf15", "other": "(wyżej)" }, + "(Select one)": { "hash": "sha256-fdfe6bf17ea5", "other": "(wybierz)" }, "Advanced": { "hash": "sha256-9f088dbebd6c", "other": "Zaawansowane" }, "Cancel": { "hash": "sha256-19766ed6ccb2", "other": "Anuluj" }, "Confirm": { "hash": "sha256-eebdd24a77d9", "other": "Potwierdź" }, diff --git a/internal/gui/text/screens.go b/internal/gui/text/screens.go index 39d8534c..fdda3cc5 100644 --- a/internal/gui/text/screens.go +++ b/internal/gui/text/screens.go @@ -2,6 +2,8 @@ package text import ( "strings" + + "github.com/donislawdev/TestingFilesGenerator/internal/legal" ) // The sentence under each work screen's title, saying what the screen DOES @@ -115,6 +117,7 @@ func SectionOutput() string { return say("SectionOutput", "Output") } func SectionPreset() string { return say("SectionPreset", "The question") } func SectionSettings() string { return say("SectionSettings", "Settings") } func SectionLicence() string { return say("SectionLicence", "Licence") } +func SectionSource() string { return say("SectionSource", "Source code") } func SectionSupport() string { return say("SectionSupport", "Support") } // The three headings over what this binary carries that somebody else wrote. @@ -774,6 +777,27 @@ func DetailDonate() string { // translated one would be a broken one. const SupportURL = "https://donislawdev.com/support/" +// ButtonGitHub opens the project's page on GitHub. Beside Donate since +// 2026-10-14, on the owner's decision, on every screen Donate stands on. +// +// The name of the site rather than "Source code" or "Report a problem", because +// the page is all of those at once - the code, the releases, the issues - and +// the name is the word a person looking for any of them already has in mind. +// Translated like every other word, although every language so far keeps it. +func ButtonGitHub() string { return say("ButtonGitHub", "GitHub") } + +// DetailGitHub is what the button leads to, written under the heading of its +// card on the About screen. +func DetailGitHub() string { + return say("DetailGitHub", "Opens the project page on GitHub in your browser - the source code, the releases and the place to report a problem.") +} + +// RepositoryURL is where the button goes. Not written here but read from the +// bill of materials, which has named it since before the button existed, so +// the two cannot come to point at different pages. Not translated, for the +// reason SupportURL gives. +const RepositoryURL = legal.Repository + // The Preferences screen: which language the window speaks, and what it keeps // between runs. Nothing on it changes the files the window makes - a setting // that did would be an input the recipe does not carry, and the command the diff --git a/internal/gui/window/about.go b/internal/gui/window/about.go index df795971..b289690a 100644 --- a/internal/gui/window/about.go +++ b/internal/gui/window/about.go @@ -63,6 +63,11 @@ func About(h Host) fyne.CanvasObject { // as a page of the application rather than as the one screen that was // left as it was. parts.Section(text.SectionLicence(), parts.Prose(paragraphs(text.Licence(version.LicenceNotice)))), + // Under the licence, which says the source is free, the page where it + // is. Since 2026-10-14, with the same button the bar of every other + // screen carries and its address under it, for the reason the Support + // card gives below. + linkCard(text.SectionSource(), text.DetailGitHub(), gitHub(h, parts.Secondary), text.RepositoryURL), // The support address, in words, on the one screen somebody reads when // they are deciding what this program costs them. // @@ -81,10 +86,7 @@ func About(h Host) fyne.CanvasObject { // whole strip of the window for one quiet word, while this card named // the same address as words nobody could press. The address stays // under the button for the reason above. - parts.Section(text.SectionSupport(), - parts.Prose(text.DetailDonate()), - container.NewHBox(donate(h, parts.Secondary)), - parts.Prose(text.SupportURL)), + linkCard(text.SectionSupport(), text.DetailDonate(), donate(h, parts.Secondary), text.SupportURL), ) sections = append(sections, carried()...) // The title with its sentence under it, as on the other three screens. @@ -104,6 +106,18 @@ func About(h Host) fyne.CanvasObject { return container.NewVScroll(page) } +// linkCard is a card whose button leads out of the program: what pressing it +// does, the button, and the address it goes to on a line of its own, for +// somebody whose desktop opens nothing. Support was the only one until +// 2026-10-14 and Source code made it two, so the shape has a name now rather +// than two copies that could come to differ (GUI rule 7). +func linkCard(heading, detail string, button *parts.Button, address string) fyne.CanvasObject { + return parts.Section(heading, + parts.Prose(detail), + container.NewHBox(button), + parts.Prose(address)) +} + // carried is what this binary contains that somebody else wrote, read out of // the build's own record. // diff --git a/internal/gui/window/generate.go b/internal/gui/window/generate.go index dad014ec..fc50dba4 100644 --- a/internal/gui/window/generate.go +++ b/internal/gui/window/generate.go @@ -265,7 +265,7 @@ func NewGenerate(host Host, links ...fyne.CanvasObject) *Generate { // foot of the form would be cut off at the edge of the viewport. g.body = g.tips.Over(container.NewBorder( nil, - g.footer(rail(append([]fyne.CanvasObject{donateButton(host)}, links...)...)), + g.footer(rail(append(outwardButtons(host), links...)...)), nil, nil, (g.keepScroll(container.NewVScroll(parts.Screen( parts.Titled(text.TabOneTarget(), text.SubtitleGenerate()), diff --git a/internal/gui/window/open.go b/internal/gui/window/open.go index df4c97db..350d93d2 100644 --- a/internal/gui/window/open.go +++ b/internal/gui/window/open.go @@ -364,14 +364,14 @@ func FirstScreen(h Host) fyne.CanvasObject { return NewGenerate(h).Object() } -// donateButton asks for money towards the work, at the left of the bar a screen -// keeps its actions in. +// outwardButtons are Donate, which asks for money towards the work, and +// GitHub, at the left of the bar a screen keeps its actions in. // // Moved there from a strip above the tabs on 2026-08-19, on the owner's // decision. It is built per screen rather than once for the window, and that is -// what the move costs: every screen has to place it, including About, which has -// no run to start and gets a bar holding nothing else. The alternative was a +// what the move costs: every screen has to place it. The alternative was a // second strip under the tabs, which is two bars at the foot of one window. +// About has no bar since 2026-09-23 and carries both buttons in cards instead. // // It stays on every screen either way, which is not decoration - a button asking // for money that appears on some screens and not others is a button people @@ -382,9 +382,14 @@ func FirstScreen(h Host) fyne.CanvasObject { // It opens the support page in whatever the desktop uses for the web. The // program fetches nothing and sends nothing, which is what keeps untouchable // rule 8 intact - see the carve out written into it on 2026-08-18. -func donateButton(h Host) fyne.CanvasObject { - // The bar's size, so its words stand level with the buttons beside them. - return donate(h, parts.Quiet).InTheBar() +// +// GitHub stands beside it since 2026-10-14, on the owner's decision, and for +// the same reason it is on every screen: it is the other button that leads out +// of the program rather than into the work. One list for the four screens, so +// a third button here is one line rather than four (GUI rule 5). +func outwardButtons(h Host) []fyne.CanvasObject { + // The bar's size, so their words stand level with the buttons beside them. + return []fyne.CanvasObject{donate(h, parts.Quiet).InTheBar(), gitHub(h, parts.Quiet).InTheBar()} } // donate is the Donate button in the look of the place it stands, with the @@ -394,6 +399,13 @@ func donate(h Host, look parts.Look) *parts.Button { return parts.NewButton(look, text.ButtonDonate(), func() { h.OpenLink(text.SupportURL) }).WithHeart() } +// gitHub is the button to the project's page, built once for the bar and the +// About card like donate. No picture in front of its word: the toolkit ships +// no mark for the site, and GitHub's own is somebody else's trademark. +func gitHub(h Host, look parts.Look) *parts.Button { + return parts.NewButton(look, text.ButtonGitHub(), func() { h.OpenLink(text.RepositoryURL) }) +} + // chooserFor is the output directory box with a way to browse to one. // // Typing a path is fine when you have one to hand and hopeless when you do not, diff --git a/internal/gui/window/preset.go b/internal/gui/window/preset.go index 71118121..18e9aa0a 100644 --- a/internal/gui/window/preset.go +++ b/internal/gui/window/preset.go @@ -100,7 +100,7 @@ func NewPreset(host Host, links ...fyne.CanvasObject) *Preset { // chooser, because they are the answer to the question that card asks. p.body = p.tips.Over(container.NewBorder( nil, - p.footer(rail(append([]fyne.CanvasObject{donateButton(host)}, links...)...)), + p.footer(rail(append(outwardButtons(host), links...)...)), nil, nil, (p.keepScroll(container.NewVScroll(parts.Screen( parts.Titled(text.TabPresets(), text.SubtitlePreset()), diff --git a/internal/gui/window/recipe.go b/internal/gui/window/recipe.go index 01b65f9e..86f0c18c 100644 --- a/internal/gui/window/recipe.go +++ b/internal/gui/window/recipe.go @@ -201,7 +201,7 @@ func NewRecipe(host Host, links ...fyne.CanvasObject) *Recipe { r.body = r.tips.Over(container.NewBorder( nil, - r.footer(rail(append([]fyne.CanvasObject{donateButton(host), parts.Divider(), r.addBtn}, links...)...)), + r.footer(rail(append(append(outwardButtons(host), parts.Divider(), r.addBtn), links...)...)), nil, nil, (r.keepScroll(container.NewVScroll(parts.Screen(parts.Titled(text.TabRecipe(), text.SubtitleRecipe()), r.baseBox, r.batchBox, r.outBox)))), )) diff --git a/internal/gui/window/tools.go b/internal/gui/window/tools.go index 807a6293..113aff86 100644 --- a/internal/gui/window/tools.go +++ b/internal/gui/window/tools.go @@ -169,7 +169,7 @@ func NewTools(host Host) *Tools { )) t.view.body = t.tips.Over(container.NewBorder( nil, - parts.ActionBar(rail(donateButton(host)), t.busy.row, roomToSpeak(bar, t.status, t.fault)), + parts.ActionBar(rail(outwardButtons(host)...), t.busy.row, roomToSpeak(bar, t.status, t.fault)), nil, nil, t.view.scroll, )) diff --git a/internal/legal/spdx.go b/internal/legal/spdx.go index 39386dc9..0a7a074d 100644 --- a/internal/legal/spdx.go +++ b/internal/legal/spdx.go @@ -29,9 +29,13 @@ import ( "strings" ) -// Where this project lives. Written here because an SBOM without a download -// location is a document nobody can act on. -const repository = "https://github.com/donislawdev/TestingFilesGenerator" +// Repository is where this project lives. Written here because an SBOM without +// a download location is a document nobody can act on. +// +// Exported since 2026-10-14, when the window got a button to the same page. One +// address in one place: the window reads it from here rather than keeping a +// copy, because two copies of an address are two places for it to go stale in. +const Repository = "https://github.com/donislawdev/TestingFilesGenerator" // ourLicence is what this program is under. // @@ -123,7 +127,7 @@ func namespace(d Document) string { seed = d.Created } sum := sha256.Sum256([]byte(d.Version + "|" + seed)) - return fmt.Sprintf("%s/spdx/%s-%x", repository, d.Version, sum[:8]) + return fmt.Sprintf("%s/spdx/%s-%x", Repository, d.Version, sum[:8]) } // add puts one binary in the document, with everything it carries beneath it. @@ -133,7 +137,7 @@ func (doc *spdxDocument) add(binary Binary, version string) { SPDXID: root, Name: binary.Name, VersionInfo: version, - DownloadLocation: repository, + DownloadLocation: Repository, FilesAnalyzed: false, LicenseConcluded: ourLicence, LicenseDeclared: ourLicence,