Skip to content

Say what the clients are, from a value rather than from a paragraph [#70] - #164

Merged
iderex merged 1 commit into
mainfrom
say-what-the-clients-are
Aug 16, 2026
Merged

Say what the clients are, from a value rather than from a paragraph [#70]#164
iderex merged 1 commit into
mainfrom
say-what-the-clients-are

Conversation

@iderex

@iderex iderex commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

What was wrong

The first thing this project says about itself is about clients, and the page
that says it offered nothing to install. The served page leads with the claim
and the landing page this build produced carried it too:

curl -sS https://flowfin.dev/ | grep -o -i 'native clients'
native clients
git show origin/main:content/index.txt | grep -c 'native clients'
2

No client exists to install. The organisation holds twelve plugin repositories
and no client repository, and the shared core has no release:

gh repo list Flowfin --limit 60 --json name --jq '[.[].name|select(startswith("jellyfin-plugin-")|not)]'
["site","lab","core",".github","hub"]
gh api repos/Flowfin/core/releases --jq 'length'
0

All four run 2026-08-16, the second against 8c46e67. The first reads the page
served today, which is produced out of another tree and is not what this branch
changes.

So a reader who arrived because of that sentence had nothing to download and
nothing on the page telling them so.

What this does

The landing page now says what the clients are meant to be and that none of
them is released, directly after the paragraph saying what this project is,
rather than at the end of the page. A reader who reads far enough to learn that
clients are part of the plan has already read the line saying none exists.

What that repairs is not the wording. A claim about whether something is
available that lives in prose is a claim somebody has to remember to change,
and the day it stops being true is the day nobody is reading that paragraph. So
the claim is data/clients.json and the sentence is composed from it: what the
clients are meant to be, and which of two availabilities holds. Setting the
value is what changes the page.

The vocabulary is deliberately not the roster's three state words. Those say
something about a plugin repository and are computed from what that repository
published. A client is not a roster row today, and borrowing a word that means
something else is the drift this milestone exists against. The day a client
becomes something a person can install it stops being a sentence here and
becomes a row with its kind named, which is where docs/roster-schema.md
holds it.

Four shapes are refused by name, because each one renders as a finished
sentence rather than as a broken page: a claim saying nothing about what the
clients are, an availability outside the two, nothing released with somewhere
to get one, and something released with nowhere to get it.

A tree that lost the file still builds, and the landing page it produces leads
with what this project is and says nothing about the clients at all, which
reads as a page about a project that has none. No reading of the produced bytes
separates a sentence that was never composed from one nobody asked for, so the
invariant verb refuses such a tree by name instead of leaving it to a row over
the output.

Closes

Nothing. It does not close #70, and the reason is one clause of that issue's
done-when rather than an omission here.

Three of the four clauses are met by this change and the fourth asks about a
table this build does not produce. The done-when asks that the state table
contain only rows the roster carries. There is no state table on any produced
page and no roster in this tree, so the clause has nothing on either side of
it, and reporting it as satisfied would be reporting a comparison that was
never made. The table is #26 and #28, and what those wait on is entry 6 of #7.

The fourth clause is recorded on the issue rather than here.

What was run

go run . ci
gate: 7 legs, in order: format, vet, test, build, links, sitemap, invariants
  needs-network was not asked for. Asking costs a request to the public name from whatever machine runs it, and a verdict that moves when somebody else's service does rather than when this tree changes. Ask with: go run ./harness/needs-network
  format: ok, 50 file(s)
  vet: ok
  test: ok, 24 test file(s)
  build: ok, 7 file(s)
  links: every reference that stays inside this site resolves to a file the build wrote
  sitemap: every page the build wrote is listed once, and every entry has a page behind it
  invariants: ok, 34 rule(s) decided, 1 owed and not decided
7 of 7 legs ran. None was skipped.

The needs-network set was not asked for, which is the line the gate prints
above rather than something left out here. No test in this change reaches the
network, opens a window, needs a display or asks for elevation, so nothing was
skipped for any of those reasons.

What the page says now:

go run . build | grep clients
read data/clients.json (none-released)
grep -o -E '<p>The clients[^<]*</p>' dist/index.html
<p>The clients this project means to build are one genuinely native client per platform, sharing a core between them. None of them is released, so there is nothing here to download and no page describing one as though there were.</p>

Both run 2026-08-16 against 8c46e67.

Two guards, each shown to bite by removing it and watching the suite go red.

The sentence follows the value. With the branch in the state being pushed, the
case passes; with the composition made to ignore the availability, so that the
same sentence is produced whatever the file says, it fails:

go test ./internal/site/ -run TestTheClientSentenceFollowsTheValueAndNotTheTemplate
ok  	github.com/Flowfin/site/internal/site	0.717s
# with the branch of clients.go that reads the value replaced by a constant condition
--- FAIL: TestTheClientSentenceFollowsTheValueAndNotTheTemplate (0.03s)
FAIL	github.com/Flowfin/site/internal/site	0.861s

A tree that lost the claim is refused. With the refusal in place the case
passes; with those lines deleted from internal/invariant/invariant.go it
fails:

go test ./internal/invariant/ -run TestRunRefusesATreeThatLostTheClaimAboutTheClients
ok  	github.com/Flowfin/site/internal/invariant	1.132s
# with the refusal deleted
--- FAIL: TestRunRefusesATreeThatLostTheClaimAboutTheClients (0.22s)
FAIL	github.com/Flowfin/site/internal/invariant	0.821s

All four runs 2026-08-16 against 8c46e67 with the stated edit applied and
reverted.

The build still produces the same bytes twice:

go run . reproduce
reproduce: two builds of ., compared byte for byte
  7 file(s), identical in both builds

go run . hygiene origin/main HEAD
hygiene: 1 non-merge commit(s) in origin/main..HEAD, origin internal
  8c46e673f1cb: subject carries its reference
1 commit(s) judged, none refused.

Both run 2026-08-16 against 8c46e67.

The means

Go and a JSON file in data/, which is what this repository already reads
every value it does not author out of, and no language, runtime or dependency
the tree does not carry. The choice is checkable in all three directions the
guide asks about. The claim is a closed set, so a value outside it is refused
by a loader rather than reviewed by a person. The two guards above are each
shown to bite by deletion. And every number and sentence quoted here carries
the command that produced it at the commit being pushed.

A prose file was the alternative and it is the thing being removed: it carries
no refusable property, and it is the shape that goes on saying something after
it has stopped being true.

Who read it

Nobody but me. This board has no second reader tonight and the ruleset requires
no approving review, so the evidence above stands in place of one rather than a
merge being read as a review.

]

The first thing this project says about itself is about clients, and the
landing page led with that sentence while no client is released. The
organisation holds twelve plugin repositories and no client repository, and
the shared core has no release, so a reader arriving because of that sentence
had nothing to download and nothing on the page telling them so.

The page now says what the clients are meant to be and that none of them is
released, directly after the paragraph saying what this project is, so a
reader learns both in the same breath rather than finding the second after
going to look for a download.

What that repairs is not the wording. A claim about whether something is
available that lives in prose is a claim somebody has to remember to change,
and the day it stops being true is the day nobody is reading that paragraph.
So the claim is data/clients.json and the sentence is composed from it: what
the clients are meant to be, and which of two availabilities holds. Setting
the value is what changes the page, and the suite flips it and asserts that
exactly that changes.

The vocabulary is deliberately not the roster's three state words. Those say
something about a plugin repository and are computed from what that repository
published; a client is not a roster row today, and borrowing a word that means
something else is the drift this milestone exists against.

Four shapes are refused by name because each renders as a finished sentence
rather than as a broken page: a claim saying nothing about what the clients
are, an availability outside the two, nothing released with somewhere to get
one, and something released with nowhere to get it.

A tree that lost the file still builds, and the landing page it produces leads
with what this project is and says nothing about the clients at all, which
reads as a project that has none. No reading of the produced bytes separates a
sentence that was never composed from one nobody asked for, so the invariant
verb refuses the tree by name instead.

The prose keeps no claim of its own about availability, and the file says so,
so the next person to edit it does not put the sentence back.

Signed-off-by: Nils Lehnen <30603423+iderex@users.noreply.github.com>
@iderex iderex added the enhancement New feature or request label Aug 16, 2026
@iderex iderex self-assigned this Aug 16, 2026
@iderex iderex added this to the The pages milestone Aug 16, 2026
@iderex
iderex merged commit 51c1b13 into main Aug 16, 2026
17 checks passed
@iderex
iderex deleted the say-what-the-clients-are branch August 16, 2026 19:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Say what the clients are without implying one exists

1 participant