Skip to content
Merged
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
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,16 @@ because it turns other people's test suites red.

### Fixed

- **The About screen now shows the support address, so the Donate button is no
longer the only way to reach it.** Pressing Donate asks your desktop to open
the page. On a machine with no browser registered that quietly does nothing,
which was the intended behaviour - a message about a button somebody pressed
out of curiosity is worse than none - except that the address itself appeared
on no screen at all, so there was nothing to fall back to.

It is on the About screen now, under Support, on a line of its own so it can
be copied in one go.

- **A manifest too big for this build to read back now says so in the report a
script reads.** A run of 25 000 files writes a manifest of about 25.9 MB
against a 16 MB reading limit, so `tfg verify` and `tfg cleanup` both refuse
Expand Down
16 changes: 0 additions & 16 deletions internal/format/opc/opc.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,22 +220,6 @@ func Settle(parts []Part, shape Shape, want int64) (Package, error) {
}
}

// Size is what a settled package will weigh. Padding is counted rather than
// written, so asking about a package of many gigabytes costs nothing.
func Size(p Package) (int64, error) {
payload := p.Filler
if payload > 0 {
p.Filler = 0
} else {
payload = 0
}
n, err := size(p)
if err != nil {
return 0, err
}
return n + payload, nil
}

func size(p Package) (int64, error) {
c := &counter{}
if err := write(context.Background(), c, p, false); err != nil {
Expand Down
19 changes: 16 additions & 3 deletions internal/guard/crowding_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,22 @@ import (
// threshold picked out of the air is a guess, and a guess written into a gate
// is a guess nobody can argue with later.
const (
// What counts as crowding. Three quarters of the ceiling is far enough from
// it that ordinary code does not trip the count, and close enough that
// something arriving there is on its way.
// What counts as crowding, as a NUMBER OF LINES rather than a share of the
// ceiling. Three quarters of the ceiling is where each of these started, and
// that is far enough from it that ordinary code does not trip the count and
// close enough that something arriving there is on its way.
//
// A starting point rather than a live ratio, and the difference is the whole
// point of these two numbers being absolute. The ceilings have come down
// since - 550 to 433 and 80 to 75 - and these have not followed, so the
// bands are tighter than three quarters today and the counts below were
// re-measured each time. That is the safe direction and it is deliberate.
//
// Do not "restore" the ratio. It was measured on 2026-08-27 and refused:
// re-deriving the band from a lowered ceiling moves it from 413 to 378,
// which pulls in seven files that did nothing and forces the cap from 2 to
// 9 - a cap going UP, which is the one direction these numbers may not go.
// A band that reshapes itself under the thing it is watching says nothing.
crowdingFileLines = 413
crowdingFunctionLines = 60

Expand Down
61 changes: 61 additions & 0 deletions internal/guard/supportaddress_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package guard

import (
"strings"
"testing"

"github.com/donislawdev/TestingFilesGenerator/internal/gui/text"
"github.com/donislawdev/TestingFilesGenerator/internal/gui/window"
)

// The address the Donate button leads to is also READABLE, in words, on a
// screen.
//
// This is not decoration and it is not a second way to say the button works.
// Handing an address to the desktop can fail - a machine with no browser
// registered is the ordinary case - and internal/gui swallows that refusal on
// purpose, with the reason written above OpenLink: there is nothing useful to
// say to somebody who pressed a button out of curiosity, and no harm done,
// because the address is on the About screen for anybody who wants to type it.
//
// That last clause was FALSE from the day the button was written until
// 2026-09-07. text.SupportURL had exactly one use in the whole tree, as the
// button's destination, so it appeared on no screen at all: a person whose
// desktop did nothing pressed Donate, saw nothing happen, and had nowhere to
// go. The decision to stay quiet was sound and rested on a fallback nobody had
// built. Found by tools/probes/guardonly, which reports exported definitions
// nothing names, not by reading the code.
//
// So the silence and the readable address are ONE decision, and this guard is
// what keeps them together. Delete the Support section and this goes red, which
// is the point: taking the address off the screen turns a considered silence
// back into a dead end.
func TestTheSupportAddressIsReadableOnAScreen(t *testing.T) {
shown := textIn(window.About(newFakeHost(t)))

if !strings.Contains(shown, text.SupportURL) {
t.Errorf("the About screen does not carry %q, so somebody whose desktop cannot open\n"+
"the Donate button has no way to reach the page - and internal/gui stays quiet\n"+
"about that failure precisely because this screen is supposed to carry it.\n"+
"The screen says:\n%s", text.SupportURL, shown)
}
}

// The address is on the screen as its own line rather than buried in a
// sentence.
//
// Somebody typing it into a browser by hand is exactly the reader this exists
// for, and an address wrapped inside a paragraph is one they have to pick out
// of prose. Asked as "a line that is the address and nothing else", which is
// the shape a person can select in one gesture.
func TestTheSupportAddressStandsOnItsOwnLine(t *testing.T) {
shown := textIn(window.About(newFakeHost(t)))

for _, line := range strings.Split(shown, "\n") {
if strings.TrimSpace(line) == text.SupportURL {
return
}
}
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)
}
Binary file modified internal/guard/testdata/screens/about.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
34 changes: 28 additions & 6 deletions internal/guard/testdata/screens/about.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@
<container size="1088x1250">
<container size="1088x1250">
<widget size="1088x1200" type="*widget.Scroll">
<container size="1088x1437">
<container pos="134,0" size="820x1437">
<container size="1088x1578">
<container pos="134,0" size="820x1578">
<container size="820x39">
<widget pos="6,0" size="808x39" type="*widget.Label">
<widget size="808x39" type="*widget.RichText">
Expand Down Expand Up @@ -73,7 +73,29 @@
</container>
</container>
</container>
<container pos="0,523" size="820x678">
<container pos="0,523" size="820x127">
<rectangle fillColor="rgba(42,42,45,255)" radius="8" size="820x127"/>
<container size="820x127">
<container pos="6,6" size="808x115">
<widget size="808x35" type="*widget.Label">
<widget size="808x35" type="*widget.RichText">
<text bold pos="6,6" size="67x23" textSize="17">Support</text>
</widget>
</widget>
<widget pos="0,44" size="808x31" type="*widget.Label">
<widget size="808x31" type="*widget.RichText">
<text pos="6,6" size="728x19">Opens the support page in your browser. The tool is free and stays free - this pays for the time that goes into it.</text>
</widget>
</widget>
<widget pos="0,84" size="808x31" type="*widget.Label">
<widget size="808x31" type="*widget.RichText">
<text pos="6,6" size="223x19">https://donislawdev.com/support/</text>
</widget>
</widget>
</container>
</container>
</container>
<container pos="0,665" size="820x678">
<rectangle fillColor="rgba(42,42,45,255)" radius="8" size="820x678"/>
<container size="820x678">
<container pos="6,6" size="808x666">
Expand Down Expand Up @@ -121,7 +143,7 @@
</container>
</container>
</container>
<container pos="0,1216" size="820x220">
<container pos="0,1357" size="820x220">
<rectangle fillColor="rgba(42,42,45,255)" radius="8" size="820x220"/>
<container size="820x220">
<container pos="6,6" size="808x208">
Expand Down Expand Up @@ -151,8 +173,8 @@
<linearGradient pos="0,-8" size="1088x8"/>
</widget>
<widget pos="1082,0" size="6x1200" type="*widget.scrollBarArea">
<widget pos="3,0" size="3x1001" type="*widget.scrollBar">
<rectangle fillColor="scrollbar" radius="3" size="3x1001"/>
<widget pos="3,0" size="3x912" type="*widget.scrollBar">
<rectangle fillColor="scrollbar" radius="3" size="3x912"/>
</widget>
</widget>
</widget>
Expand Down
4 changes: 0 additions & 4 deletions internal/gui/parts/parts.go
Original file line number Diff line number Diff line change
Expand Up @@ -491,10 +491,6 @@ const (
GapSection = 14
)

// SectionGap is the older name for GapSection, kept because that is the name
// the recipe screen and the guards were written against.
const SectionGap = GapSection

// Column stacks its children with one fixed gap, whatever the theme's padding
// is.
//
Expand Down
8 changes: 8 additions & 0 deletions internal/gui/run_cgo.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,14 @@ func (d desktop) ChooseDirectory(chosen func(string)) {
// somebody whose desktop has no browser registered, no screen to say it on that
// would not be a modal about a button they pressed by curiosity, and no harm
// done - the address is in the About screen for anybody who wants to type it.
//
// That last clause was FALSE until 2026-09-07 and is the reason this paragraph
// is worth reading twice. text.SupportURL had exactly one use in the whole
// tree, as this button's destination, so it was on no screen at all and a
// person whose desktop did nothing had nowhere to go. The decision to stay
// quiet was sound and rested on a fallback that did not exist. The About screen
// carries the address now, in its Support section, which is what makes the
// sentence above true rather than merely reasonable.
func (d desktop) OpenLink(address string) {
parsed, err := url.Parse(address)
if err != nil {
Expand Down
12 changes: 4 additions & 8 deletions internal/gui/text/locale/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -288,10 +288,6 @@
"description": "Shown in the window.",
"other": "Nothing was written. Check the settings marked above."
},
"SectionBatches": {
"description": "The heading over a group of fields.",
"other": "Batches"
},
"SectionCarriedCode": {
"description": "The heading over a group of fields.",
"other": "Third party code compiled in"
Expand Down Expand Up @@ -324,6 +320,10 @@
"description": "The heading over a group of fields.",
"other": "Settings"
},
"SectionSupport": {
"description": "The heading over a group of fields.",
"other": "Support"
},
"SettingKey": {
"description": "Shown in the window. Carries one value, {{.Key}}, which has to stay spelled exactly that way.",
"other": "Written as {{.Key}} in a recipe."
Expand Down Expand Up @@ -376,10 +376,6 @@
"description": "Shown in the window.",
"other": "Working out what this would cost..."
},
"WritingFiles": {
"description": "Shown in the window. Carries one value, {{.Files}}, which has to stay spelled exactly that way.",
"other": "Writing {{.Files}}..."
},
"WritingTo": {
"description": "Shown in the window. Carries one value, {{.Directory}}, which has to stay spelled exactly that way.",
"other": "Files will go to {{.Directory}}"
Expand Down
6 changes: 3 additions & 3 deletions internal/gui/text/screens.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,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 SectionSupport() string { return say("SectionSupport", "Support") }

// The two headings over what this binary carries that somebody else wrote.
//
Expand Down Expand Up @@ -280,9 +281,8 @@ func TooManyFiles(count int64, reason error) string {
// difference between the two screens is how many batches, not how advanced
// the person is. "Advanced" would have said the other screen is for
// beginners, which is not true of anybody generating one batch of files.
func HeadingRecipe() string { return say("HeadingRecipe", "Run several batches together") }
func TabRecipe() string { return say("TabRecipe", "Several batches") }
func SectionBatches() string { return say("SectionBatches", "Batches") }
func HeadingRecipe() string { return say("HeadingRecipe", "Run several batches together") }
func TabRecipe() string { return say("TabRecipe", "Several batches") }

// BatchHeading names one batch in the list, counted the way the refusals count.
//
Expand Down
6 changes: 0 additions & 6 deletions internal/gui/text/text.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,12 +115,6 @@ func WritingTo(dir string) string {
return sayf("WritingTo", "Files will go to {{.Directory}}", map[string]any{"Directory": dir})
}

// WritingFiles is the line under the bar at the moment a run starts, before
// the first progress report arrives.
func WritingFiles(count int) string {
return sayf("WritingFiles", "Writing {{.Files}}...", map[string]any{"Files": files(count)})
}

// WorkingOutTheCost is what a preview says while it is going.
//
// A preview does disk work - it asks how much room there is and whether any of
Expand Down
14 changes: 14 additions & 0 deletions internal/gui/window/about.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,20 @@ 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(version.LicenceNotice)),
// The support address, in words, on the one screen somebody reads when
// they are deciding what this program costs them.
//
// It is here because the Donate button cannot always work and says so:
// handing an address to the desktop fails on a machine with no browser
// registered, and that refusal is swallowed on purpose, because there is
// no useful thing to say to somebody who pressed a button out of
// curiosity. That decision only holds while the address is READABLE
// somewhere, and until 2026-09-07 it was not - the constant had exactly
// one use in the whole tree, as the button's destination, so a person
// whose desktop did nothing had nowhere to go. The comment beside
// OpenLink said this screen carried it. This is that screen carrying it.
parts.Section(text.SectionSupport(),
parts.Prose(text.DetailDonate()), parts.Prose(text.SupportURL)),
}
sections = append(sections, carried()...)
page := parts.Screen(text.HeadingAbout(version.Version), sections...)
Expand Down
11 changes: 11 additions & 0 deletions internal/recipe/compose.go
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,17 @@ func firstControl(value string) (rune, bool) {
// name for a given box: both sides can agree and both be wrong. That is what
// TestEveryRefusalAboutABatchMarksThatBatchsBox is for, and the two together are
// what make the pairing safe rather than merely consistent.
//
// Eleven of these have no caller today and every one of them stays. This is a
// VOCABULARY rather than a list of things in use: it names the keys the recipe
// schema has, and half a vocabulary is the "format added halfway" shape that
// the exhaustive linter is switched on to catch one axis over. Five of the
// eleven - locale, policy, engine, extends, with - name settings this build
// refuses out loud with "not in this build yet", so a set holding only the keys
// that happen to have a consumer would be missing exactly the ones a refusal
// has to be able to name. Measured 2026-09-07 by tools/probes/guardonly, which
// is where the eleven came from, and written here so the next scan does not
// reach for the delete.
const (
KeyTargets = core.KeyTargets
KeyVersion = "version"
Expand Down
Loading