Gates for the developer guide defects Asciidoctor cannot see - #5669

Open
shai-almog wants to merge 17 commits into
masterfrom
devguide-guards
Open

Gates for the developer guide defects Asciidoctor cannot see#5669
shai-almog wants to merge 17 commits into
masterfrom
devguide-guards

Conversation

@shai-almog

Copy link
Copy Markdown
Collaborator

Split out of #5664 so the structural repairs there stop waiting on tooling review. Stacked on it, because these checks are written against a guide that #5664 fixes — run them on master and several fail by design.

Four checks. Each was verified to fail when the defect it describes is reintroduced, and to pass again when it is reverted.

CheckCatchesAsciidoctor's behaviour
check-guide-structure.pya document in the tree but not in the book; a chapter that silently becomes a part, a subsection, or a duplicate; an included chapter whose title does not survive into the rendered outputsilent
check-guide-xrefs.pyan internal link that resolves to nothing, and one that renders as a bare [some-id]silent — a reference with link text renders as an ordinary link to nowhere
check-missing-code-blocks.pyprose that promises a listing where a hole followsnot its job
check-guide-links.pylinks checked against the paths the site actually servesnot its job

How the link checker decides

It does not carry a list of good links. It derives what the site serves and follows the rules to what they produce:

  • _redirects sources compiled to patterns, splat and :placeholder included, then followed to their destination rather than accepted for matching. /*.html matches every root-level .html path there is, so a match proves nothing.
  • Hugo routes from front matter — url, slug, aliases — because 1055 of the 1262 content pages override their route with slug, and deriving it from the filename both invents routes and rejects real ones.
  • Draft and future-dated pages excluded, since Hugo defaults buildDrafts and buildFuture to false.
  • Static assets enumerated as files; a directory without an index.html is not a route.
  • /developer-guide/ derived from what scripts/website/build.sh rsyncs. Only /javadoc/ stays exempt, because it is produced from the framework sources and cannot be enumerated here.

Ratchets

check-missing-code-blocks.py records 406 existing holes and check-guide-links.py38 bad links. Both are multisets — one line per occurrence, because a file naming the same bad URL twice has two problems.

Both may only shrink. A fixed entry that is still baselined fails, so the ratchet actually banks its fixes; growing one requires --allow-new, so recording new debt is deliberate rather than a side effect of regenerating.

Where they run

developer-guide-docs.yml runs all four. check-guide-links.py also runs from website-docs.yml, which triggers on docs/website/** — a page renamed or re-slugged there breaks the guide, and putting those paths into the guide workflow would run the whole guide build, maven install and screenshot generation included, for every blog post the daily publisher pushes.

The docs paths filter now covers scripts/developer-guide/** rather than two named files, because a change to a script not in that filter started the workflow while skipping the steps that script governs.

@chatgpt-codex-connector

chatgpt-codex-connectorBot commented Sep 2, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

ReviewStatusCommitReview trigger
📝 Code Review🔄 Running since 2026-09-02T15:38:41.729150Za9c0b73New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:2da41dd8c7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-links.py Outdated
Comment threadscripts/developer-guide/check-guide-structure.py

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:28f950b311

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-links.py

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:21e513112f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-links.py Outdated
@github-actions

Copy link
Copy Markdown
Contributor

Cloudflare Preview

@github-actions

github-actionsBot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Developer Guide build artifacts are available for download from this workflow run:

Developer Guide quality checks:

  • AsciiDoc linter: No issues found (report)
  • Vale: No alerts found (report)
  • Paragraph capitalization: No paragraph capitalization issues (report)
  • LanguageTool: No grammar matches (report)
  • Image references: No unused images detected (report)

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:6d0560cc05

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-structure.py
Comment threadscripts/developer-guide/check-guide-links.py

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:b96d1321a1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-links.py Outdated
Comment threadscripts/developer-guide/check-guide-links.py
@shai-almog
shai-almogforce-pushed the devguide-structure-and-guards branch from 6108cad to e113f04CompareSeptember 2, 2026 11:57

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:d82c0ea525

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-structure.py

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:42189c33d9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-links.py
Comment threadscripts/developer-guide/check-guide-links.py

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:89e588ddc8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-missing-code-blocks.py Outdated
Comment threadscripts/developer-guide/check-guide-xrefs.py Outdated

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:b88150d821

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-xrefs.py
Comment threadscripts/developer-guide/check-guide-structure.py Outdated
Base automatically changed from devguide-structure-and-guards to masterSeptember 2, 2026 13:19
shai-almogand others added 10 commits September 2, 2026 16:20
Four checks, split out of the structural repairs they were written alongside so
each can be reviewed on its own terms. Every one was verified to fail when the
defect it describes is reintroduced, and to pass again when it is reverted.
check-guide-structure.py -- every document is in the book or declared out of it;
no chapter silently becomes a part, a subsection, or a duplicate; and, by
rendering the book and reading its headings back, every included chapter's title
survives into the output. That last check is what would have caught a whole
chapter rendering as subsections of the one before it.
check-guide-xrefs.py -- every internal link resolves, and none renders as a bare
"[some-id]". Asciidoctor reports neither: a reference carrying link text renders
as an ordinary link to nowhere.
check-missing-code-blocks.py -- prose that promises a listing where a hole
follows. 406 exist, from the snippet extraction in bbdc605, recoverable from
bbdc605~1.
check-guide-links.py -- links checked against the paths the site actually
serves, derived from _redirects and the Hugo content tree and followed through
every redirect rule to what it produces, rather than accepted for matching one.
The two ratchets may only shrink; growing one needs --allow-new, so recording
debt is deliberate rather than a side effect of regenerating.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The stacked content change restored the video capture constraints examples, so
their baseline entries went stale and the ratchet refused to pass until they
were removed. 406 to 402.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The outcome check compared counts of normalized headings at any depth, which a
collision satisfies: "Analytics" is a chapter and also a subsection of Commerce,
so the rendered book contains it twice. If the chapter were swallowed, the
subsection kept the count at one and the check -- the central safeguard of this
change -- passed. Measured on the current book: analytics appears twice at any
level and once at chapter level; "getting started" four times at any level and
never at chapter level.
Manifest entries are now counted against chapter-level headings only. Nested
fragments still use the any-level count, because their depth is whatever their
parent gives them.
Second finding: resolves() tried every matching redirect rule and passed if any
chain worked. The host applies the FIRST match and stops, so a link matching an
early rule that leads somewhere deleted would have passed on the strength of a
later rule the reader never reaches. It now returns the first match's result. No
guide link changes verdict today; the point is that the model matches the
behaviour it claims to model.
Also declined, with the reasoning left in the code rather than a review thread:
adding Markdown's three-backtick fence to the literal-block list. The guide
contains none, and validate-guide-snippets.py requires every listing to be
[source,LANG] with a bare include:: inside ---- delimiters, so such a block
would fail that gate before reaching this one.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The pattern required empty brackets, which is right for ifdef and ifndef -- with
content they are the single-line form and guard only that line -- but wrong for
ifeval, which has no single-line form and always carries its expression in the
brackets. So the directive was listed and never recognised.
Latent: the manifest contains no ifeval, and its only conditionals are two
single-line ifdef attribute assignments, which are still correctly treated as
not opening a block. Fixed because a pattern that names three directives and
handles two is a bug in what the code claims, not a missing feature.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review asked for AsciiDoc comment-state tracking so that a URL inside a `//`
line or a `////` block is skipped. Declining, and recording it where the next
reader of this loop will look rather than in a review thread nobody reads.
The guide has 120 files, zero commented-out URLs and zero `////` blocks, so the
tracking would govern nothing that exists. It would also weaken the gate: with
comments skipped, commenting a line out makes its finding disappear and lets the
ratchet shrink, banking a "fix" while the dead link stays in the source waiting
to be uncommented. Deleting the link is the fix.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review asked for fragment validation on same-site URLs. Measuring first: the
guide holds 42 same-site URLs with a fragment, and 38 are /javadoc/ -- the one
tree this script cannot enumerate, because it is generated from the framework
sources at build time. The other four all pointed at
`developer-guide.html#<anchor>`: this book, linking into its own body.
Those are the same defect as the fifteen `/manual/*.html` deep links already
converted in this stack, missed because they use a different path. They send a
reader out to the web to fetch the page they are reading, they do not work in
the PDF, and a renamed section breaks them silently -- the anchor lives in the
rendered book, so no amount of site-path checking can see it.
So the fix is not to validate the fragment, it is to stop writing the link:
all four become `<<anchor,text>>` xrefs, which check-guide-xrefs.py already
resolves against the rendered anchors. All three targets exist today, so this
repairs no live 404 -- it moves four unguarded links under a gate.
The rule closes the class rather than the four instances: any codenameone.com
link whose path is one of this book's own routes and which carries a fragment is
now reported. Verified non-vacuous by restoring one of the four and watching it
fail, then reverting.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both findings describe constructs the guide does not contain, so neither is a
live defect. Both are real holes in the checkers though, and each closes for a
few lines, so they close rather than getting argued with.
Conditional includes (check-guide-structure.py). A manifest entry inside an
ifdef/ifeval was skipped by the duplicate count -- correctly, since the same
chapter under two exclusive branches is one chapter in the output -- and also by
the rendered-title check, since only one branch renders. Skipping both means a
chapter included twice inside a single ACTIVE branch would pass silently. Rather
than teach the checker to distinguish exclusive branches for a construct that
does not exist (measured: zero include:: lines sit inside a conditional anywhere
in the guide), a conditional manifest entry is now refused outright, with a
message saying what to extend if one is ever wanted.
Trailing slashes on file paths (check-guide-links.py). Site paths were compared
with the trailing slash stripped, so a link to `/x.jar/` was validated against
`/x.jar`. The reviewer's premise checks out: _redirects declares the two forms as
separate routes and spells both out where both work, 32 such pairs. Preserving
the slash everywhere would break the many legitimate directory links, so the
narrow case is reported instead -- a last path segment containing a dot, wearing
a trailing slash. Directory routes such as /blog/ and /javadoc/com/codename1/io/
have no dot in the last segment and are untouched.
Both rules verified non-vacuous by introducing an instance and reading the real
exit status rather than a pipeline's: structure and links each exit 1 on the
probe and 0 once it is reverted.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review is right that the rendered-title check has slack: it counts normalized
titles across the whole book, so a nested fragment losing its heading can be
covered by an identical heading elsewhere. Confirmed rather than accepted --
removing the leveloffset from both Maven includes takes "Getting started" from 4
rendered headings to 3, while only one file declares it, so `3 >= 1` and the gate
passed a genuinely swallowed section.
Rather than give nested fragments a rendered identity, which the HTML does not
carry, this checks the cause. The spacing rule already existed but ran only on
the root manifest, and its condition was "a blank line follows". Both were wrong.
Reproduced minimally to find the real rule -- a heading is absorbed only when ALL
of these hold:
* the include is followed immediately by content, and
* neither this include nor the following one carries leveloffset (asciidoctor
brackets the content with :leveloffset: attribute entries, and an attribute
entry closes the paragraph -- the one on the FOLLOWING include lands between
the paragraph and the heading, so it protects just as well), and
* the included file does not end on a blank line, and
* its last line is ordinary paragraph text -- a delimiter, table row, heading,
attribute entry or comment all close the paragraph. That is why
_generated-build-hints.adoc, which ends on "|===", does not eat the
"Versioned builds" heading directly after it.
Against the real book, all four combinations now agree with what asciidoctor
actually renders:
both leveloffset 4 headings renders ok gate passes
only current 4 headings renders ok gate passes
only following 4 headings renders ok gate passes
neither 3 headings SWALLOWED gate fails
An earlier draft exempted only the current include and failed the third row --
a false positive on markup that renders correctly. Every guide include is now
checked, not just the manifest's: measured across all 120 files, this leaves zero
findings, because the five adjacent includes in Maven-Project-Workflow.asciidoc
are protected by leveloffset.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… the rest
Three link findings, and only the first was a live defect -- in the rule I added
one commit earlier.
The trailing-slash rule was unconditional, so it reported "/videos.html/" as
broken even though _redirects declares that exact source and the site serves it.
The rules here all compile slash-insensitively ("^...$/?"), which is right for
matching but discards the distinction the site actually draws, so the raw sources
are now collected alongside them and a slashed file path is reported only when
_redirects does not spell it out. Probed both ways: "/videos.html/" passes,
"/files/CodenameOneBuildClient.jar/" fails.
Hugo route overrides. hugo.toml sets neither [permalinks] nor uglyURLs today, so
deriving a route as "section path + slug" is currently correct; if either
appears, every route underneath moves and this would go on accepting links to
paths Hugo no longer publishes. Modelling a configuration that is not there would
be guesswork, and reading the built public/ tree makes a local run depend on a
tree that may be stale or absent. So it now notices instead: either key present
aborts the check with what to do about it. Probed with each key in turn.
Protocol-relative links and fragments on ordinary same-site pages are declined,
with the measurements recorded in the code rather than in a review thread nobody
reads. The guide has no `link://` macro at all, and its one bare "//host/path" is
a JavaScript string inside a source block, so widening URL_RE to match "//" would
start reporting code as a broken link. And of the 42 same-site URLs carrying a
fragment, 38 are /javadoc/ -- generated at build time, exempt for that reason --
while the other four pointed into this book and are now xrefs, which
check-guide-xrefs.py resolves against the rendered anchors. Nothing is left that
this script could check without building the Hugo site.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ting
Two more holes in the guards. Neither has an instance in the guide today, and
both close cheaply enough that arguing about them would cost more.
check-guide-xrefs.py rendered the book once, with the default backend, so
anything inside ifdef::backend-pdf[] was dropped before a single reference in it
could be examined -- while the workflow publishes an asciidoctor-pdf build from
the same source. Native-Themes.asciidoc already has two such branches. It now
renders twice, the second time with -a backend-pdf, which selects exactly the
content the PDF build includes without needing asciidoctor-pdf here. The two runs
are checked SEPARATELY rather than pooled: an anchor that exists only in the HTML
branch must not satisfy a reference made in the PDF branch. Findings name the
branch they came from.
A/B with a dangling xref planted inside the backend-pdf branch of
Native-Themes.asciidoc:
old "Cross-references OK: 1741 anchors" -- exit 0
new "Native-Themes.asciidoc:506: <<definitely-not-a-real-anchor>> ...
(1 reference(s), pdf render)" -- exit 1
check-missing-code-blocks.py accepted any `[attribute]` line as the start of the
promised block, so an admonition standing where a listing used to be hid the
hole. An admonition is prose and can never be the listing a sentence promised, so
the five names are excluded. Excluded by name rather than by whitelisting the
kinds that ARE code: measured, the bracket lines legitimately answering a
promising sentence already span [source] (582), [cols=...] and [options=...] (56),
[listing] (3), [quote] (2) and an anchored image, and a whitelist would report the
next kind nobody anticipated.
A/B with each block planted after a promising sentence, using the real signature
of a removed listing -- the two blank lines it leaves behind:
[NOTE] old: none new new: reported as a hole
[source,java] old: none new new: none new
The baseline is unchanged at 402, because the guide currently has no admonition
sitting in a listing's place.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…separator
Two findings, and this time the first one is live.
The rendered book contains exactly three relative hrefs, and all three are
broken: css.asciidoc carried link:Images[], link:Fonts[] and
link:Supported-Properties#text-decoration[] over from the wiki this guide
replaced, naming pages that were never carried across with them. The guide
renders as a single page, so a relative href resolves against wherever that page
is served and reaches nothing that ships with it -- the reader gets a 404 while
every gate reported success. All three targets are sections in that same file:
[[Images]] already existed, [[Fonts]] is added to match it, and
[[text-decoration]] was already there and already used by a sibling reference.
check-guide-xrefs.py now reports any relative href, which after this change has
zero instances. Absolute URLs stay with check-guide-links.py, and root-relative
paths and fragments are excluded. A/B with one link restored: old printed
"Cross-references OK" and exited 0, new reports it and exits 1.
The second is a wrong claim in my own comment. check-guide-structure.py returned
early when an include was followed by ifdef/ifndef/ifeval/endif, on the reasoning
that "a preprocessor directive is not content and cannot absorb a paragraph".
True, and irrelevant: asciidoctor REMOVES the directive during preprocessing, so
it does not separate anything either. Measured -- with `ifdef::backend-html5[]`
between an include ending in prose and a following heading, the render contains
zero heading tags and the paragraph reads "...preferred tools. === A parent-local
heading". The rule now scans past directive lines to the first line that survives
preprocessing. A/B on that construct: old passed it, new reports it.
Note the rendered-title check cannot cover this case either, because a
parent-local heading is not the first heading of any included file.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:f9e50cb8cc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-links.py
Comment threadscripts/developer-guide/check-guide-xrefs.py
… has a limit
The first finding was live: the link ratchet held a working link.
docs/website/functions/[[path]].js runs after context.next() has already 404ed
and then redirects anything under /files/ or /demos/ to
download.codenameone.com. Those paths are served, so the model was missing a
whole class of route and had banked a real one -- /files/iOS_UI-Kit.psd -- as
broken. The destination is off-site, which puts it in the same bucket as every
other off-site redirect: reachable, not verifiable from this repository. The
rules are appended AFTER the _redirects rules because the function is a fallback
and the first matching rule wins, mirroring how the host evaluates. The baseline
shrinks 37 -> 36, and a new /files/ link is now accepted rather than failing CI.
The second is a real limit of the PDF surrogate, and one the command line cannot
remove. Setting backend-pdf makes ifndef::backend-pdf[] content disappear and
ifdef::backend-pdf[] content appear -- verified, and that is the only form this
guide uses. It does not undefine backend-html5: the HTML converter sets that
itself, after command-line attributes are applied, so even `-a backend-html5!`
leaves it defined. Measured both ways. Content guarded on the HTML backend would
therefore survive into the surrogate render and could satisfy a PDF-only
reference that the real asciidoctor-pdf build leaves dangling.
The guide has no such conditional, so rather than model a construct that is not
there -- or drive asciidoctor-pdf and try to read anchors back out of a PDF --
the checker refuses one if it appears, naming the alternative the rest of the
guide already uses. Probed: planting an ifdef::backend-html5[] block aborts the
check with that message.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:6a8963d02a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-structure.py Outdated
…side one
Both scanners matched a delimiter of exactly four characters, so a block opened
with ----- was invisible to them and its contents were scanned as live markup.
The guide has 20 such lines -- eight in Desktop-Integration, six in
Working-With-iOS, six in appendix_goal_generate_archetype -- every one an
ordinary [source] block wrapping an include::. validate-guide-snippets.py already
accepted -{4,}; these two had drifted from it.
Nothing is misread today, because those includes name .java, .xml and .properties
files and the reachability walk skips non-asciidoc targets. The failure mode is
still the interesting one though: it is a FALSE POSITIVE generator, not a missed
defect. A/B with each construct planted inside a five-dash block:
a displayed include::Working-with-UWP.asciidoc[]
old reported a swallowed-heading error against markup that only appears
on the page as text
new clean
a displayed "The wrapper looks like this:" followed by blank lines
old reported it as a missing code block
new clean
Both now track the delimiter that opened the block and close on one of the same
character AND length, which is what asciidoctor does -- so a four-dash line
inside a five-dash block is content rather than the close. The set stays limited
to literal blocks: an example (====) or sidebar (****) block contains live
markup, so headings and includes inside one are real and must keep counting.
Baselines unchanged: 119 included documents, 402 holes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:57212b7f43

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-structure.py
Comment threadscripts/developer-guide/check-guide-xrefs.py
shai-almogand others added 2 commits September 2, 2026 18:21
* Guide figures render with a bundled font instead of the host's
None of the 24 generated figures could be reproduced outside CI. Running the
generator on a Mac reproduces 0 of 24 byte-for-byte, with 3-20% of pixels
differing. Two consecutive local runs are byte-identical, so the generator is
deterministic on one host; the variable is the font.
`PreAdvancedThemingScreenshots` styled with `Font.createSystemFont`, which
resolves through `JavaSEPort.fontFaceSystem` -- "Arial" on macOS and Linux
alike. Arial exists on a developer's Mac and not on a stock CI runner, so AWT
silently substitutes and every glyph changes. The differences are exactly that:
for `flow-layout.png` every differing pixel sits in y 10-130, the title and
label rows, while the colored blocks below match to the pixel.
So the byte-exact gate has only ever been green because CI both generates and
verifies. A developer regenerating locally could not match it, and at the scale
the guide's remaining ~260 app screenshots would need, that is untenable.
The figures now load `native:MainRegular`, which `JavaSEPort.loadTrueTypeFont`
reads from `/com/codename1/impl/javase/Roboto-Medium.ttf` on the classpath
rather than from an installed-font lookup. This is also what the project's font
rule requires everywhere: never `createSystemFont`, always the `native:` scheme.
A null return refuses loudly rather than falling back to a host font, because a
silent fallback would restore the exact non-determinism this removes.
The committed baselines are regenerated from a Mac. CI byte-compares them on
Linux, so the check either passes -- proving host independence rather than
asserting it -- or fails and says so immediately.
Composition, dimensions and colours are unchanged; only the typeface moves.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Pin the font on every component, not just the ones that get block colours
CI reported one stale figure out of 24: `guibuilder-2-insets-3.png`, the only
one containing a `TextField`. The other 23 matched Linux byte for byte, so the
bundled-font change worked -- it just did not reach far enough.
`applyBlockStyleToContent` styled `Label` and `Button`. Everything else kept the
theme's default font, which resolves through the host, so the text field and its
hint were still host-dependent. Enumerating the types that carry text would have
left the next one added broken the same way, so the walk now pins the face on
every component it visits, plus the hint label, which is painted by a `Label`
that is not in the component tree and so is never reached by the walk.
The first attempt used `BLOCK_FONT` for this and regressed the figure: at 29px
against the theme default's 13px the field grew and squeezed "Submit" down to a
clipped sliver. `FIELD_FONT` is sized to reproduce the original height, so the
composition is unchanged and only the typeface moves.
Verified locally by rendering twice, once with `JavaSEPort.setFontFaces` pointed
at a family that does not exist -- which is what a machine without Arial looks
like to the port. All 24 come out byte-identical, so nothing in these figures
reads an installed font any more.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Publish what the runner actually rendered when a screenshot fails
"Committed screenshot is stale: <name>" names the file and nothing else, so
there is no way to tell a real regression from an environment difference
without adding a debugging round trip to CI. The generated directory is now
uploaded as an artifact when the step fails.
Needed immediately: one figure still differs between a Mac and the runner after
the font fix, and host fonts, JDK version and working directory have each been
ruled out locally.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Pin MigLayout's platform, and stop demanding byte equality of a glyph edge
The artifact step paid for itself immediately. Downloading what the runner
rendered showed two figures differing, not the one the gate reported -- it exits
on the first mismatch -- and the two had nothing in common.
`mig-layout.png` differed by 12.8% of its pixels, a real layout shift.
MigLayout takes its default gaps from `PlatformDefaults`, which reads
`System.getProperty("os.name")` and answers MAC_OSX, GNOME or WINDOWS_XP, each
with different spacing. The figure was rendering with macOS gaps on a Mac and
GNOME gaps on the runner. Pinning the platform fixes it: with the pin, 23 of the
24 figures now match the runner's own output byte for byte.
`layered-layout.png` differed by 173 pixels, 0.113%, inside a 25x25 box. That
one is not fixable. Measured against the runner's bytes, the material glyph
lands at exactly the same size and the same origin -- a 55x49 bounding box --
and differs only in antialiased edge coverage, 946 fully-white pixels against
916. Java2D rasterizes the same glyph, from the same bundled font, at the same
size, slightly differently on the two platforms. Demanding byte equality there
would mean deleting legitimate content from the figure or carrying a
permanently red check.
So the comparison moves from `cmp -s` to a comparer that still requires byte
equality by default and accepts a bounded difference only where a figure carries
a `.tolerance` sidecar explaining itself, in the same key=value shape the CN1SS
suites already use. The area bound does the work: a per-pixel delta that large is
meaningless on its own, since a glyph edge flips between white and the block
behind it, but a regression that changed the icon would move far more than 0.3%
of the image.
Verified by running the comparer with the runner's own output against the
committed figures -- which is exactly what CI will do -- and by four probes: an
untoleranced figure that differs fails, the toleranced figure fails when changed
beyond its budget, a missing figure fails the count, and a clean run passes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Guard the iOS font branch, and stop the comparator gating itself out
Two review findings on this PR.
The first says `native:MainRegular` does not give host-independent output,
because `loadTrueTypeFont` resolves native fonts to the first installed SF or
Helvetica family before reaching the bundled Roboto. The branch is real, but it
is reached only when `isIOS` is set, which `loadSkinFile` does for a skin whose
systemFontFamily contains "helvetica" -- and this generator never loads a skin.
The measurement agrees: figures rendered on a Mac match the Linux runner byte
for byte, which could not happen if one side were resolving Helvetica Neue and
the other Roboto.
So the conclusion does not hold today, but the risk is real for tomorrow: a
change that loads a skin here would put host fonts back into the output with no
other symptom. The generator now refuses to run under an iOS platform, and says
why, rather than leaving that to a comment nobody reads.
The second finding is straightforwardly right and is the more serious of the
two. `on.pull_request.paths` triggers on `scripts/developer-guide/**`, but the
`Determine changed components` filter named only two scripts, so a pull request
touching any other script here started the workflow with `docs` false and
skipped the steps that script governs. A change to compare-screenshots.py could
have merged without the screenshot check ever running it -- a gate that skips
itself. The filter now covers the whole directory, which also removes the
two-copies-drift the surrounding comment already warns about.
Verified the guard changes no output: all 24 figures are unchanged, and the
comparer still passes against the runner's own bytes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Count every changed pixel toward the area budget, not just the loud ones
The comparator incremented its mismatch counter only for pixels whose channel
delta EXCEEDED maxChannelDelta, which is what the CN1SS comparator does. With a
sidecar written the way this one was -- a large delta bound paired with a small
area bound -- that leaves an unbounded hole, and review gave the exploit in the
figures' own palette: recolouring the green #06a806 to #a608a6 moves every
channel by exactly 160, so with maxChannelDelta=160 not one pixel is counted and
a dramatically different image reports zero mismatches.
Reproduced it before fixing: the recolour changes 4191 pixels, 2.73% of the
image, at a worst delta of exactly 160 -- and passed.
The two bounds are now independent. maxMismatchPercent limits how much of the
image may change at all, counting every differing pixel; maxChannelDelta caps how
far any single pixel may move. The measured legitimate noise -- 173 pixels,
0.113%, worst delta 141 -- still passes, the recolour now fails on area, and a
five-pixel solid overwrite fails on delta.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Correct the sidecar's rationale to match how the bounds now work
The previous commit changed the comparator so every differing pixel counts
toward the area budget and the channel delta is a separate ceiling. The sidecar
still explained the old behaviour -- that the area bound did the work and the
delta was meaningless on its own -- which is now wrong in a file whose whole
purpose is to justify the numbers beside it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Compare figures in RGBA, because they have an alpha channel
The comparator converted both images to RGB before counting differences. The
committed figures are genuine RGBA PNGs -- four channels, currently opaque
everywhere -- so that dropped a real channel, and any change confined to alpha
was invisible to the tolerance path. A regression that turned the whole figure
transparent while leaving every colour channel intact reported zero changed
pixels and passed.
Byte equality, which every figure without a sidecar is still held to, always
caught this. Only a figure carrying a sidecar could reach the weakened path, so
today the exposure was one image -- but that image is exactly the one whose
comparison is relaxed.
Verified: making layered-layout.png fully transparent with its RGB channels
untouched now reports 100% of pixels changed, the real runner output still
passes, and the recolour and untoleranced-difference probes still fail as they
did.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…page
Two findings, one of them a false positive I introduced last round.
Comment blocks. Asciidoctor drops a //// block entirely, so an include inside
one does not put its target in the book and prose inside one promises the reader
nothing -- but both scanners read straight through. //// joins the fence set in
each. A/B with each construct planted inside a comment block: a displayed
include:: made the structure check fail, and a promising sentence made the
missing-block check fail; both are clean now.
Note this is deliberately NOT symmetric with check-guide-links.py, which still
reads URLs inside comments. The reasons differ and the code says so: a dead link
commented out is still dead debt in the source, and letting the ratchet shrink
for it would make "comment the line out" a way to silence that gate. Reachability
is the opposite -- calling a commented-out include reachable states something
about the book that is simply untrue.
Packaged assets. The relative-href rule added last round was too broad: the HTML
packaging step copies every subdirectory of docs/developer-guide next to
developer-guide.html, so link:img/example.png[] resolves for a reader who opens
the zip, and the gate would have rejected it. A relative href that names an
existing file under the guide directory is now allowed. sketch/ is not, because
that is the one directory the packaging loop skips.
Probed all four ways, since an exemption is only worth having if it still
rejects:
link:img/flow-layout.png[] exit 0 (real, packaged)
link:img/does-not-exist.png[] exit 1
link:sketch/whatever.svg[] exit 1 (not packaged)
link:Supported-Properties[] exit 1 (the wiki-era shape)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:a6bd5197dc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-xrefs.py Outdated
Comment threadscripts/developer-guide/check-guide-xrefs.py
shai-almogand others added 2 commits September 2, 2026 18:35
Both findings are defects in the two rules added in the previous commit.
packaged_asset() asked whether the file exists here, which is not the question. A
link only works if the published output carries it, and the two outputs filter
differently:
* the HTML archive copies only the SUBDIRECTORIES of docs/developer-guide next
to developer-guide.html and skips sketch, so a root-level file is never in the
zip however much it exists in the tree;
* the website rsync excludes sketch/, *.asciidoc and *.adoc, so a source file
nested inside a packaged directory still does not reach the site.
So link:Introduction.asciidoc[] was being exempted and readers would follow it to
nothing. Both filters are now reproduced. The permissive direction is the
expensive one here -- it suppresses a finding for a link nobody can follow --
so all four cases are probed:
img/flow-layout.png exit 0 in both outputs
Introduction.asciidoc exit 1 in neither
img/does-not-exist.png exit 1
sketch/foo.svg exit 1 excluded by both
The backend-conditional rejection scanned raw lines, so a source block DISPLAYING
`ifdef::backend-html5[]` as example text aborted the whole gate. A displayed
directive takes no part in preprocessing. It now tracks literal and comment block
delimiters the way the other guide scanners do. A/B: displayed inside a source
block went 1 -> 0, while a genuinely active conditional still exits 1.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…18 statistics (#5671)
* The Introduction taught a dead IDE menu, an iTunes walkthrough and 2018 statistics
Six corrections to the chapter a new developer reads first:
* The JavaScript target was described twice and contradicted itself: the
overview said TeaVM did the translation, the port section said ParparVM with
TeaVM as a fallback. The port section is right -- `javascript.port` is
declared with `parparvm` as its default and `teavm` documented as "the
original builder as a compatibility fallback".
* `http://teavm.org:[TeaVM-based builder]` -- the stray colon made the macro a
bare host, and the site serves TLS. Removed from the link ratchet, which now
stands at 37.
* Bitcode was cited as something ParparVM absorbed without modification. Apple
has since withdrawn bitcode, which makes the point better than the arrival
did, so the sentence now names both moves.
* The device-fragmentation section rested on two 2018 Android share numbers,
followed by a sentence conceding they would be stale on arrival. The
structural claim survives without them.
* The iOS developer fee was dated with "for 10 years at the time of this
writing".
* The UDID instructions walked the reader through iTunes, with a screenshot of
iTunes on iOS 9.3.5 that also exposed a real device serial and UDID. iTunes
has not existed on macOS since Catalina, and the signing chapter already gives
the current answer, so this now points there.
* The device build was "a right click away" via the IDE plugin, illustrated by a
menu still offering Blackberry, J2ME and Windows Phone builds. Replaced with
the `cn1:buildAndroid` and `cn1:buildIos` goals, taken from the mojo names.
Both screenshots are deleted, so find_unused_images.py stays satisfied.
Verified: asciidoctor at --failure-level WARN, Vale at suggestion level,
LanguageTool (status ok, 0 matches, run under JDK 17 rather than the JDK 8
false green), paragraph capitalization, snippets, xrefs, structure, links and
missing-code-blocks all clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* "-pl common" made a device build a no-op that reported success
Review caught the command I had just written. AbstractBuildWrapperMojo.execute()
opens with:
if (!project.isExecutionRoot()) {
getLog().info("Skipping execution for non-root project");
return;
}
and BuildAndroidMojo, BuildIosMojo and BuildWin32Mojo all inherit it unchanged.
Under `-pl common` the selected module is not the execution root, so the goal
logs that line and stops. The design is deliberate -- the wrapper re-invokes
Maven on the root pom itself (`request.setPomFile(new File("pom.xml"))`) -- so
naming a module both skips the wrapper and defeats its purpose.
Measured rather than reasoned, against scripts/hellocodenameone:
mvn -pl common ...:buildWin32 -> "Skipping execution for non-root project"
BUILD SUCCESS, nothing built
mvn ...:buildWin32 -> no skip line, proceeds into the reactor
A silent success is the worst failure mode for a getting-started instruction, so
the Introduction now says where to run it and warns about the module form
explicitly.
Working-With-Windows carried the same `-pl common` shape and is fixed with it --
it is where I copied the form from, so leaving it would reintroduce the defect
the next time someone follows the pattern.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* The Windows build wrappers named a platform no profile answers to
Review checked the command this PR documents and found it still builds nothing.
The wrapper passes `codename1.platform` to its nested Maven run, and that
property is what activates the module profile in a generated project's root pom.
The profile is `win`, matching the value the win module itself declares:
<profile><id>win</id>
<activation><property>
<name>codename1.platform</name><value>win</value>
</property></activation>
<modules><module>win</module></modules>
BuildWin32Mojo and BuildWindowsDeviceMojo both passed "windows", which matches no
profile at all. Nothing else in the plugin reads the platform as "windows", so
the value was simply inert: the win module never joined the reactor and the
nested build reported success having produced no Windows binary. Every other
wrapper already agrees with its profile -- android, ios, javascript, linux and
javase all match -- so these two were the only ones out of step.
Verified by A/B against scripts/hellocodenameone, reading the NESTED reactor
rather than the outer one:
before hellocodenameone, -common, -javase <- no win module
after hellocodenameone, -common, -win
The build TARGET is a separate namespace and stays "windows-device"
(Executor.BUILD_TARGET_WINDOWS_NATIVE), which is correct.
SpotBugs over codenameone-maven-plugin regenerated: 0 findings.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* The primary Win32 command had both defects the convenience goal just lost
Fixing BuildWin32Mojo made the sentence beside this snippet false: the goal now
submits a build, while the command the chapter leads with still could not. It
carried both defects at once --
mvn -pl common package -Dcodename1.platform=windows ...
-- `-pl common` builds that module instead of the reactor, so the win module is
never reached, and `windows` matches no profile even when the reactor is whole.
The two mistakes hid each other: with only the module selected, the platform
value had nothing left to activate.
Corrected to what the wrapper actually runs, read off AbstractBuildWrapperMojo:
goal `package`, `codename1.platform=win`, `codename1.buildTarget=windows-device`,
from the project root.
A/B against scripts/hellocodenameone, reading the reactor, which Maven prints
before it compiles:
old no win module in the reactor at all
new hellocodenameone, -common, -win
Both invocations then fail identically on this machine with "invalid target
release: 17", because tools/env.sh pins JDK 8 while the demo targets 17. That is
environmental and equal on both sides, so it does not affect the comparison.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@shai-almog
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Gates for the developer guide defects Asciidoctor cannot see - #5669

Open
shai-almog wants to merge 17 commits into
masterfrom
devguide-guards
Open

Gates for the developer guide defects Asciidoctor cannot see#5669
shai-almog wants to merge 17 commits into
masterfrom
devguide-guards

Conversation

@shai-almog

Copy link
Copy Markdown
Collaborator

Split out of #5664 so the structural repairs there stop waiting on tooling review. Stacked on it, because these checks are written against a guide that #5664 fixes — run them on master and several fail by design.

Four checks. Each was verified to fail when the defect it describes is reintroduced, and to pass again when it is reverted.

CheckCatchesAsciidoctor's behaviour
check-guide-structure.pya document in the tree but not in the book; a chapter that silently becomes a part, a subsection, or a duplicate; an included chapter whose title does not survive into the rendered outputsilent
check-guide-xrefs.pyan internal link that resolves to nothing, and one that renders as a bare [some-id]silent — a reference with link text renders as an ordinary link to nowhere
check-missing-code-blocks.pyprose that promises a listing where a hole followsnot its job
check-guide-links.pylinks checked against the paths the site actually servesnot its job

How the link checker decides

It does not carry a list of good links. It derives what the site serves and follows the rules to what they produce:

  • _redirects sources compiled to patterns, splat and :placeholder included, then followed to their destination rather than accepted for matching. /*.html matches every root-level .html path there is, so a match proves nothing.
  • Hugo routes from front matter — url, slug, aliases — because 1055 of the 1262 content pages override their route with slug, and deriving it from the filename both invents routes and rejects real ones.
  • Draft and future-dated pages excluded, since Hugo defaults buildDrafts and buildFuture to false.
  • Static assets enumerated as files; a directory without an index.html is not a route.
  • /developer-guide/ derived from what scripts/website/build.sh rsyncs. Only /javadoc/ stays exempt, because it is produced from the framework sources and cannot be enumerated here.

Ratchets

check-missing-code-blocks.py records 406 existing holes and check-guide-links.py38 bad links. Both are multisets — one line per occurrence, because a file naming the same bad URL twice has two problems.

Both may only shrink. A fixed entry that is still baselined fails, so the ratchet actually banks its fixes; growing one requires --allow-new, so recording new debt is deliberate rather than a side effect of regenerating.

Where they run

developer-guide-docs.yml runs all four. check-guide-links.py also runs from website-docs.yml, which triggers on docs/website/** — a page renamed or re-slugged there breaks the guide, and putting those paths into the guide workflow would run the whole guide build, maven install and screenshot generation included, for every blog post the daily publisher pushes.

The docs paths filter now covers scripts/developer-guide/** rather than two named files, because a change to a script not in that filter started the workflow while skipping the steps that script governs.

@chatgpt-codex-connector

chatgpt-codex-connectorBot commented Sep 2, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

ReviewStatusCommitReview trigger
📝 Code Review🔄 Running since 2026-09-02T15:38:41.729150Za9c0b73New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:2da41dd8c7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-links.py Outdated
Comment threadscripts/developer-guide/check-guide-structure.py

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:28f950b311

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-links.py

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:21e513112f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-links.py Outdated
@github-actions

Copy link
Copy Markdown
Contributor

Cloudflare Preview

@github-actions

github-actionsBot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Developer Guide build artifacts are available for download from this workflow run:

Developer Guide quality checks:

  • AsciiDoc linter: No issues found (report)
  • Vale: No alerts found (report)
  • Paragraph capitalization: No paragraph capitalization issues (report)
  • LanguageTool: No grammar matches (report)
  • Image references: No unused images detected (report)

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:6d0560cc05

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-structure.py
Comment threadscripts/developer-guide/check-guide-links.py

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:b96d1321a1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-links.py Outdated
Comment threadscripts/developer-guide/check-guide-links.py
@shai-almog
shai-almogforce-pushed the devguide-structure-and-guards branch from 6108cad to e113f04CompareSeptember 2, 2026 11:57

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:d82c0ea525

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-structure.py

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:42189c33d9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-links.py
Comment threadscripts/developer-guide/check-guide-links.py

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:89e588ddc8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-missing-code-blocks.py Outdated
Comment threadscripts/developer-guide/check-guide-xrefs.py Outdated

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:b88150d821

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-xrefs.py
Comment threadscripts/developer-guide/check-guide-structure.py Outdated
Base automatically changed from devguide-structure-and-guards to masterSeptember 2, 2026 13:19
shai-almogand others added 10 commits September 2, 2026 16:20
Four checks, split out of the structural repairs they were written alongside so
each can be reviewed on its own terms. Every one was verified to fail when the
defect it describes is reintroduced, and to pass again when it is reverted.
check-guide-structure.py -- every document is in the book or declared out of it;
no chapter silently becomes a part, a subsection, or a duplicate; and, by
rendering the book and reading its headings back, every included chapter's title
survives into the output. That last check is what would have caught a whole
chapter rendering as subsections of the one before it.
check-guide-xrefs.py -- every internal link resolves, and none renders as a bare
"[some-id]". Asciidoctor reports neither: a reference carrying link text renders
as an ordinary link to nowhere.
check-missing-code-blocks.py -- prose that promises a listing where a hole
follows. 406 exist, from the snippet extraction in bbdc605, recoverable from
bbdc605~1.
check-guide-links.py -- links checked against the paths the site actually
serves, derived from _redirects and the Hugo content tree and followed through
every redirect rule to what it produces, rather than accepted for matching one.
The two ratchets may only shrink; growing one needs --allow-new, so recording
debt is deliberate rather than a side effect of regenerating.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The stacked content change restored the video capture constraints examples, so
their baseline entries went stale and the ratchet refused to pass until they
were removed. 406 to 402.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The outcome check compared counts of normalized headings at any depth, which a
collision satisfies: "Analytics" is a chapter and also a subsection of Commerce,
so the rendered book contains it twice. If the chapter were swallowed, the
subsection kept the count at one and the check -- the central safeguard of this
change -- passed. Measured on the current book: analytics appears twice at any
level and once at chapter level; "getting started" four times at any level and
never at chapter level.
Manifest entries are now counted against chapter-level headings only. Nested
fragments still use the any-level count, because their depth is whatever their
parent gives them.
Second finding: resolves() tried every matching redirect rule and passed if any
chain worked. The host applies the FIRST match and stops, so a link matching an
early rule that leads somewhere deleted would have passed on the strength of a
later rule the reader never reaches. It now returns the first match's result. No
guide link changes verdict today; the point is that the model matches the
behaviour it claims to model.
Also declined, with the reasoning left in the code rather than a review thread:
adding Markdown's three-backtick fence to the literal-block list. The guide
contains none, and validate-guide-snippets.py requires every listing to be
[source,LANG] with a bare include:: inside ---- delimiters, so such a block
would fail that gate before reaching this one.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The pattern required empty brackets, which is right for ifdef and ifndef -- with
content they are the single-line form and guard only that line -- but wrong for
ifeval, which has no single-line form and always carries its expression in the
brackets. So the directive was listed and never recognised.
Latent: the manifest contains no ifeval, and its only conditionals are two
single-line ifdef attribute assignments, which are still correctly treated as
not opening a block. Fixed because a pattern that names three directives and
handles two is a bug in what the code claims, not a missing feature.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review asked for AsciiDoc comment-state tracking so that a URL inside a `//`
line or a `////` block is skipped. Declining, and recording it where the next
reader of this loop will look rather than in a review thread nobody reads.
The guide has 120 files, zero commented-out URLs and zero `////` blocks, so the
tracking would govern nothing that exists. It would also weaken the gate: with
comments skipped, commenting a line out makes its finding disappear and lets the
ratchet shrink, banking a "fix" while the dead link stays in the source waiting
to be uncommented. Deleting the link is the fix.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review asked for fragment validation on same-site URLs. Measuring first: the
guide holds 42 same-site URLs with a fragment, and 38 are /javadoc/ -- the one
tree this script cannot enumerate, because it is generated from the framework
sources at build time. The other four all pointed at
`developer-guide.html#<anchor>`: this book, linking into its own body.
Those are the same defect as the fifteen `/manual/*.html` deep links already
converted in this stack, missed because they use a different path. They send a
reader out to the web to fetch the page they are reading, they do not work in
the PDF, and a renamed section breaks them silently -- the anchor lives in the
rendered book, so no amount of site-path checking can see it.
So the fix is not to validate the fragment, it is to stop writing the link:
all four become `<<anchor,text>>` xrefs, which check-guide-xrefs.py already
resolves against the rendered anchors. All three targets exist today, so this
repairs no live 404 -- it moves four unguarded links under a gate.
The rule closes the class rather than the four instances: any codenameone.com
link whose path is one of this book's own routes and which carries a fragment is
now reported. Verified non-vacuous by restoring one of the four and watching it
fail, then reverting.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both findings describe constructs the guide does not contain, so neither is a
live defect. Both are real holes in the checkers though, and each closes for a
few lines, so they close rather than getting argued with.
Conditional includes (check-guide-structure.py). A manifest entry inside an
ifdef/ifeval was skipped by the duplicate count -- correctly, since the same
chapter under two exclusive branches is one chapter in the output -- and also by
the rendered-title check, since only one branch renders. Skipping both means a
chapter included twice inside a single ACTIVE branch would pass silently. Rather
than teach the checker to distinguish exclusive branches for a construct that
does not exist (measured: zero include:: lines sit inside a conditional anywhere
in the guide), a conditional manifest entry is now refused outright, with a
message saying what to extend if one is ever wanted.
Trailing slashes on file paths (check-guide-links.py). Site paths were compared
with the trailing slash stripped, so a link to `/x.jar/` was validated against
`/x.jar`. The reviewer's premise checks out: _redirects declares the two forms as
separate routes and spells both out where both work, 32 such pairs. Preserving
the slash everywhere would break the many legitimate directory links, so the
narrow case is reported instead -- a last path segment containing a dot, wearing
a trailing slash. Directory routes such as /blog/ and /javadoc/com/codename1/io/
have no dot in the last segment and are untouched.
Both rules verified non-vacuous by introducing an instance and reading the real
exit status rather than a pipeline's: structure and links each exit 1 on the
probe and 0 once it is reverted.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review is right that the rendered-title check has slack: it counts normalized
titles across the whole book, so a nested fragment losing its heading can be
covered by an identical heading elsewhere. Confirmed rather than accepted --
removing the leveloffset from both Maven includes takes "Getting started" from 4
rendered headings to 3, while only one file declares it, so `3 >= 1` and the gate
passed a genuinely swallowed section.
Rather than give nested fragments a rendered identity, which the HTML does not
carry, this checks the cause. The spacing rule already existed but ran only on
the root manifest, and its condition was "a blank line follows". Both were wrong.
Reproduced minimally to find the real rule -- a heading is absorbed only when ALL
of these hold:
* the include is followed immediately by content, and
* neither this include nor the following one carries leveloffset (asciidoctor
brackets the content with :leveloffset: attribute entries, and an attribute
entry closes the paragraph -- the one on the FOLLOWING include lands between
the paragraph and the heading, so it protects just as well), and
* the included file does not end on a blank line, and
* its last line is ordinary paragraph text -- a delimiter, table row, heading,
attribute entry or comment all close the paragraph. That is why
_generated-build-hints.adoc, which ends on "|===", does not eat the
"Versioned builds" heading directly after it.
Against the real book, all four combinations now agree with what asciidoctor
actually renders:
both leveloffset 4 headings renders ok gate passes
only current 4 headings renders ok gate passes
only following 4 headings renders ok gate passes
neither 3 headings SWALLOWED gate fails
An earlier draft exempted only the current include and failed the third row --
a false positive on markup that renders correctly. Every guide include is now
checked, not just the manifest's: measured across all 120 files, this leaves zero
findings, because the five adjacent includes in Maven-Project-Workflow.asciidoc
are protected by leveloffset.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… the rest
Three link findings, and only the first was a live defect -- in the rule I added
one commit earlier.
The trailing-slash rule was unconditional, so it reported "/videos.html/" as
broken even though _redirects declares that exact source and the site serves it.
The rules here all compile slash-insensitively ("^...$/?"), which is right for
matching but discards the distinction the site actually draws, so the raw sources
are now collected alongside them and a slashed file path is reported only when
_redirects does not spell it out. Probed both ways: "/videos.html/" passes,
"/files/CodenameOneBuildClient.jar/" fails.
Hugo route overrides. hugo.toml sets neither [permalinks] nor uglyURLs today, so
deriving a route as "section path + slug" is currently correct; if either
appears, every route underneath moves and this would go on accepting links to
paths Hugo no longer publishes. Modelling a configuration that is not there would
be guesswork, and reading the built public/ tree makes a local run depend on a
tree that may be stale or absent. So it now notices instead: either key present
aborts the check with what to do about it. Probed with each key in turn.
Protocol-relative links and fragments on ordinary same-site pages are declined,
with the measurements recorded in the code rather than in a review thread nobody
reads. The guide has no `link://` macro at all, and its one bare "//host/path" is
a JavaScript string inside a source block, so widening URL_RE to match "//" would
start reporting code as a broken link. And of the 42 same-site URLs carrying a
fragment, 38 are /javadoc/ -- generated at build time, exempt for that reason --
while the other four pointed into this book and are now xrefs, which
check-guide-xrefs.py resolves against the rendered anchors. Nothing is left that
this script could check without building the Hugo site.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ting
Two more holes in the guards. Neither has an instance in the guide today, and
both close cheaply enough that arguing about them would cost more.
check-guide-xrefs.py rendered the book once, with the default backend, so
anything inside ifdef::backend-pdf[] was dropped before a single reference in it
could be examined -- while the workflow publishes an asciidoctor-pdf build from
the same source. Native-Themes.asciidoc already has two such branches. It now
renders twice, the second time with -a backend-pdf, which selects exactly the
content the PDF build includes without needing asciidoctor-pdf here. The two runs
are checked SEPARATELY rather than pooled: an anchor that exists only in the HTML
branch must not satisfy a reference made in the PDF branch. Findings name the
branch they came from.
A/B with a dangling xref planted inside the backend-pdf branch of
Native-Themes.asciidoc:
old "Cross-references OK: 1741 anchors" -- exit 0
new "Native-Themes.asciidoc:506: <<definitely-not-a-real-anchor>> ...
(1 reference(s), pdf render)" -- exit 1
check-missing-code-blocks.py accepted any `[attribute]` line as the start of the
promised block, so an admonition standing where a listing used to be hid the
hole. An admonition is prose and can never be the listing a sentence promised, so
the five names are excluded. Excluded by name rather than by whitelisting the
kinds that ARE code: measured, the bracket lines legitimately answering a
promising sentence already span [source] (582), [cols=...] and [options=...] (56),
[listing] (3), [quote] (2) and an anchored image, and a whitelist would report the
next kind nobody anticipated.
A/B with each block planted after a promising sentence, using the real signature
of a removed listing -- the two blank lines it leaves behind:
[NOTE] old: none new new: reported as a hole
[source,java] old: none new new: none new
The baseline is unchanged at 402, because the guide currently has no admonition
sitting in a listing's place.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…separator
Two findings, and this time the first one is live.
The rendered book contains exactly three relative hrefs, and all three are
broken: css.asciidoc carried link:Images[], link:Fonts[] and
link:Supported-Properties#text-decoration[] over from the wiki this guide
replaced, naming pages that were never carried across with them. The guide
renders as a single page, so a relative href resolves against wherever that page
is served and reaches nothing that ships with it -- the reader gets a 404 while
every gate reported success. All three targets are sections in that same file:
[[Images]] already existed, [[Fonts]] is added to match it, and
[[text-decoration]] was already there and already used by a sibling reference.
check-guide-xrefs.py now reports any relative href, which after this change has
zero instances. Absolute URLs stay with check-guide-links.py, and root-relative
paths and fragments are excluded. A/B with one link restored: old printed
"Cross-references OK" and exited 0, new reports it and exits 1.
The second is a wrong claim in my own comment. check-guide-structure.py returned
early when an include was followed by ifdef/ifndef/ifeval/endif, on the reasoning
that "a preprocessor directive is not content and cannot absorb a paragraph".
True, and irrelevant: asciidoctor REMOVES the directive during preprocessing, so
it does not separate anything either. Measured -- with `ifdef::backend-html5[]`
between an include ending in prose and a following heading, the render contains
zero heading tags and the paragraph reads "...preferred tools. === A parent-local
heading". The rule now scans past directive lines to the first line that survives
preprocessing. A/B on that construct: old passed it, new reports it.
Note the rendered-title check cannot cover this case either, because a
parent-local heading is not the first heading of any included file.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:f9e50cb8cc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-links.py
Comment threadscripts/developer-guide/check-guide-xrefs.py
… has a limit
The first finding was live: the link ratchet held a working link.
docs/website/functions/[[path]].js runs after context.next() has already 404ed
and then redirects anything under /files/ or /demos/ to
download.codenameone.com. Those paths are served, so the model was missing a
whole class of route and had banked a real one -- /files/iOS_UI-Kit.psd -- as
broken. The destination is off-site, which puts it in the same bucket as every
other off-site redirect: reachable, not verifiable from this repository. The
rules are appended AFTER the _redirects rules because the function is a fallback
and the first matching rule wins, mirroring how the host evaluates. The baseline
shrinks 37 -> 36, and a new /files/ link is now accepted rather than failing CI.
The second is a real limit of the PDF surrogate, and one the command line cannot
remove. Setting backend-pdf makes ifndef::backend-pdf[] content disappear and
ifdef::backend-pdf[] content appear -- verified, and that is the only form this
guide uses. It does not undefine backend-html5: the HTML converter sets that
itself, after command-line attributes are applied, so even `-a backend-html5!`
leaves it defined. Measured both ways. Content guarded on the HTML backend would
therefore survive into the surrogate render and could satisfy a PDF-only
reference that the real asciidoctor-pdf build leaves dangling.
The guide has no such conditional, so rather than model a construct that is not
there -- or drive asciidoctor-pdf and try to read anchors back out of a PDF --
the checker refuses one if it appears, naming the alternative the rest of the
guide already uses. Probed: planting an ifdef::backend-html5[] block aborts the
check with that message.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:6a8963d02a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-structure.py Outdated
…side one
Both scanners matched a delimiter of exactly four characters, so a block opened
with ----- was invisible to them and its contents were scanned as live markup.
The guide has 20 such lines -- eight in Desktop-Integration, six in
Working-With-iOS, six in appendix_goal_generate_archetype -- every one an
ordinary [source] block wrapping an include::. validate-guide-snippets.py already
accepted -{4,}; these two had drifted from it.
Nothing is misread today, because those includes name .java, .xml and .properties
files and the reachability walk skips non-asciidoc targets. The failure mode is
still the interesting one though: it is a FALSE POSITIVE generator, not a missed
defect. A/B with each construct planted inside a five-dash block:
a displayed include::Working-with-UWP.asciidoc[]
old reported a swallowed-heading error against markup that only appears
on the page as text
new clean
a displayed "The wrapper looks like this:" followed by blank lines
old reported it as a missing code block
new clean
Both now track the delimiter that opened the block and close on one of the same
character AND length, which is what asciidoctor does -- so a four-dash line
inside a five-dash block is content rather than the close. The set stays limited
to literal blocks: an example (====) or sidebar (****) block contains live
markup, so headings and includes inside one are real and must keep counting.
Baselines unchanged: 119 included documents, 402 holes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:57212b7f43

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-structure.py
Comment threadscripts/developer-guide/check-guide-xrefs.py
shai-almogand others added 2 commits September 2, 2026 18:21
* Guide figures render with a bundled font instead of the host's
None of the 24 generated figures could be reproduced outside CI. Running the
generator on a Mac reproduces 0 of 24 byte-for-byte, with 3-20% of pixels
differing. Two consecutive local runs are byte-identical, so the generator is
deterministic on one host; the variable is the font.
`PreAdvancedThemingScreenshots` styled with `Font.createSystemFont`, which
resolves through `JavaSEPort.fontFaceSystem` -- "Arial" on macOS and Linux
alike. Arial exists on a developer's Mac and not on a stock CI runner, so AWT
silently substitutes and every glyph changes. The differences are exactly that:
for `flow-layout.png` every differing pixel sits in y 10-130, the title and
label rows, while the colored blocks below match to the pixel.
So the byte-exact gate has only ever been green because CI both generates and
verifies. A developer regenerating locally could not match it, and at the scale
the guide's remaining ~260 app screenshots would need, that is untenable.
The figures now load `native:MainRegular`, which `JavaSEPort.loadTrueTypeFont`
reads from `/com/codename1/impl/javase/Roboto-Medium.ttf` on the classpath
rather than from an installed-font lookup. This is also what the project's font
rule requires everywhere: never `createSystemFont`, always the `native:` scheme.
A null return refuses loudly rather than falling back to a host font, because a
silent fallback would restore the exact non-determinism this removes.
The committed baselines are regenerated from a Mac. CI byte-compares them on
Linux, so the check either passes -- proving host independence rather than
asserting it -- or fails and says so immediately.
Composition, dimensions and colours are unchanged; only the typeface moves.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Pin the font on every component, not just the ones that get block colours
CI reported one stale figure out of 24: `guibuilder-2-insets-3.png`, the only
one containing a `TextField`. The other 23 matched Linux byte for byte, so the
bundled-font change worked -- it just did not reach far enough.
`applyBlockStyleToContent` styled `Label` and `Button`. Everything else kept the
theme's default font, which resolves through the host, so the text field and its
hint were still host-dependent. Enumerating the types that carry text would have
left the next one added broken the same way, so the walk now pins the face on
every component it visits, plus the hint label, which is painted by a `Label`
that is not in the component tree and so is never reached by the walk.
The first attempt used `BLOCK_FONT` for this and regressed the figure: at 29px
against the theme default's 13px the field grew and squeezed "Submit" down to a
clipped sliver. `FIELD_FONT` is sized to reproduce the original height, so the
composition is unchanged and only the typeface moves.
Verified locally by rendering twice, once with `JavaSEPort.setFontFaces` pointed
at a family that does not exist -- which is what a machine without Arial looks
like to the port. All 24 come out byte-identical, so nothing in these figures
reads an installed font any more.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Publish what the runner actually rendered when a screenshot fails
"Committed screenshot is stale: <name>" names the file and nothing else, so
there is no way to tell a real regression from an environment difference
without adding a debugging round trip to CI. The generated directory is now
uploaded as an artifact when the step fails.
Needed immediately: one figure still differs between a Mac and the runner after
the font fix, and host fonts, JDK version and working directory have each been
ruled out locally.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Pin MigLayout's platform, and stop demanding byte equality of a glyph edge
The artifact step paid for itself immediately. Downloading what the runner
rendered showed two figures differing, not the one the gate reported -- it exits
on the first mismatch -- and the two had nothing in common.
`mig-layout.png` differed by 12.8% of its pixels, a real layout shift.
MigLayout takes its default gaps from `PlatformDefaults`, which reads
`System.getProperty("os.name")` and answers MAC_OSX, GNOME or WINDOWS_XP, each
with different spacing. The figure was rendering with macOS gaps on a Mac and
GNOME gaps on the runner. Pinning the platform fixes it: with the pin, 23 of the
24 figures now match the runner's own output byte for byte.
`layered-layout.png` differed by 173 pixels, 0.113%, inside a 25x25 box. That
one is not fixable. Measured against the runner's bytes, the material glyph
lands at exactly the same size and the same origin -- a 55x49 bounding box --
and differs only in antialiased edge coverage, 946 fully-white pixels against
916. Java2D rasterizes the same glyph, from the same bundled font, at the same
size, slightly differently on the two platforms. Demanding byte equality there
would mean deleting legitimate content from the figure or carrying a
permanently red check.
So the comparison moves from `cmp -s` to a comparer that still requires byte
equality by default and accepts a bounded difference only where a figure carries
a `.tolerance` sidecar explaining itself, in the same key=value shape the CN1SS
suites already use. The area bound does the work: a per-pixel delta that large is
meaningless on its own, since a glyph edge flips between white and the block
behind it, but a regression that changed the icon would move far more than 0.3%
of the image.
Verified by running the comparer with the runner's own output against the
committed figures -- which is exactly what CI will do -- and by four probes: an
untoleranced figure that differs fails, the toleranced figure fails when changed
beyond its budget, a missing figure fails the count, and a clean run passes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Guard the iOS font branch, and stop the comparator gating itself out
Two review findings on this PR.
The first says `native:MainRegular` does not give host-independent output,
because `loadTrueTypeFont` resolves native fonts to the first installed SF or
Helvetica family before reaching the bundled Roboto. The branch is real, but it
is reached only when `isIOS` is set, which `loadSkinFile` does for a skin whose
systemFontFamily contains "helvetica" -- and this generator never loads a skin.
The measurement agrees: figures rendered on a Mac match the Linux runner byte
for byte, which could not happen if one side were resolving Helvetica Neue and
the other Roboto.
So the conclusion does not hold today, but the risk is real for tomorrow: a
change that loads a skin here would put host fonts back into the output with no
other symptom. The generator now refuses to run under an iOS platform, and says
why, rather than leaving that to a comment nobody reads.
The second finding is straightforwardly right and is the more serious of the
two. `on.pull_request.paths` triggers on `scripts/developer-guide/**`, but the
`Determine changed components` filter named only two scripts, so a pull request
touching any other script here started the workflow with `docs` false and
skipped the steps that script governs. A change to compare-screenshots.py could
have merged without the screenshot check ever running it -- a gate that skips
itself. The filter now covers the whole directory, which also removes the
two-copies-drift the surrounding comment already warns about.
Verified the guard changes no output: all 24 figures are unchanged, and the
comparer still passes against the runner's own bytes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Count every changed pixel toward the area budget, not just the loud ones
The comparator incremented its mismatch counter only for pixels whose channel
delta EXCEEDED maxChannelDelta, which is what the CN1SS comparator does. With a
sidecar written the way this one was -- a large delta bound paired with a small
area bound -- that leaves an unbounded hole, and review gave the exploit in the
figures' own palette: recolouring the green #06a806 to #a608a6 moves every
channel by exactly 160, so with maxChannelDelta=160 not one pixel is counted and
a dramatically different image reports zero mismatches.
Reproduced it before fixing: the recolour changes 4191 pixels, 2.73% of the
image, at a worst delta of exactly 160 -- and passed.
The two bounds are now independent. maxMismatchPercent limits how much of the
image may change at all, counting every differing pixel; maxChannelDelta caps how
far any single pixel may move. The measured legitimate noise -- 173 pixels,
0.113%, worst delta 141 -- still passes, the recolour now fails on area, and a
five-pixel solid overwrite fails on delta.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Correct the sidecar's rationale to match how the bounds now work
The previous commit changed the comparator so every differing pixel counts
toward the area budget and the channel delta is a separate ceiling. The sidecar
still explained the old behaviour -- that the area bound did the work and the
delta was meaningless on its own -- which is now wrong in a file whose whole
purpose is to justify the numbers beside it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Compare figures in RGBA, because they have an alpha channel
The comparator converted both images to RGB before counting differences. The
committed figures are genuine RGBA PNGs -- four channels, currently opaque
everywhere -- so that dropped a real channel, and any change confined to alpha
was invisible to the tolerance path. A regression that turned the whole figure
transparent while leaving every colour channel intact reported zero changed
pixels and passed.
Byte equality, which every figure without a sidecar is still held to, always
caught this. Only a figure carrying a sidecar could reach the weakened path, so
today the exposure was one image -- but that image is exactly the one whose
comparison is relaxed.
Verified: making layered-layout.png fully transparent with its RGB channels
untouched now reports 100% of pixels changed, the real runner output still
passes, and the recolour and untoleranced-difference probes still fail as they
did.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…page
Two findings, one of them a false positive I introduced last round.
Comment blocks. Asciidoctor drops a //// block entirely, so an include inside
one does not put its target in the book and prose inside one promises the reader
nothing -- but both scanners read straight through. //// joins the fence set in
each. A/B with each construct planted inside a comment block: a displayed
include:: made the structure check fail, and a promising sentence made the
missing-block check fail; both are clean now.
Note this is deliberately NOT symmetric with check-guide-links.py, which still
reads URLs inside comments. The reasons differ and the code says so: a dead link
commented out is still dead debt in the source, and letting the ratchet shrink
for it would make "comment the line out" a way to silence that gate. Reachability
is the opposite -- calling a commented-out include reachable states something
about the book that is simply untrue.
Packaged assets. The relative-href rule added last round was too broad: the HTML
packaging step copies every subdirectory of docs/developer-guide next to
developer-guide.html, so link:img/example.png[] resolves for a reader who opens
the zip, and the gate would have rejected it. A relative href that names an
existing file under the guide directory is now allowed. sketch/ is not, because
that is the one directory the packaging loop skips.
Probed all four ways, since an exemption is only worth having if it still
rejects:
link:img/flow-layout.png[] exit 0 (real, packaged)
link:img/does-not-exist.png[] exit 1
link:sketch/whatever.svg[] exit 1 (not packaged)
link:Supported-Properties[] exit 1 (the wiki-era shape)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:a6bd5197dc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-xrefs.py Outdated
Comment threadscripts/developer-guide/check-guide-xrefs.py
shai-almogand others added 2 commits September 2, 2026 18:35
Both findings are defects in the two rules added in the previous commit.
packaged_asset() asked whether the file exists here, which is not the question. A
link only works if the published output carries it, and the two outputs filter
differently:
* the HTML archive copies only the SUBDIRECTORIES of docs/developer-guide next
to developer-guide.html and skips sketch, so a root-level file is never in the
zip however much it exists in the tree;
* the website rsync excludes sketch/, *.asciidoc and *.adoc, so a source file
nested inside a packaged directory still does not reach the site.
So link:Introduction.asciidoc[] was being exempted and readers would follow it to
nothing. Both filters are now reproduced. The permissive direction is the
expensive one here -- it suppresses a finding for a link nobody can follow --
so all four cases are probed:
img/flow-layout.png exit 0 in both outputs
Introduction.asciidoc exit 1 in neither
img/does-not-exist.png exit 1
sketch/foo.svg exit 1 excluded by both
The backend-conditional rejection scanned raw lines, so a source block DISPLAYING
`ifdef::backend-html5[]` as example text aborted the whole gate. A displayed
directive takes no part in preprocessing. It now tracks literal and comment block
delimiters the way the other guide scanners do. A/B: displayed inside a source
block went 1 -> 0, while a genuinely active conditional still exits 1.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…18 statistics (#5671)
* The Introduction taught a dead IDE menu, an iTunes walkthrough and 2018 statistics
Six corrections to the chapter a new developer reads first:
* The JavaScript target was described twice and contradicted itself: the
overview said TeaVM did the translation, the port section said ParparVM with
TeaVM as a fallback. The port section is right -- `javascript.port` is
declared with `parparvm` as its default and `teavm` documented as "the
original builder as a compatibility fallback".
* `http://teavm.org:[TeaVM-based builder]` -- the stray colon made the macro a
bare host, and the site serves TLS. Removed from the link ratchet, which now
stands at 37.
* Bitcode was cited as something ParparVM absorbed without modification. Apple
has since withdrawn bitcode, which makes the point better than the arrival
did, so the sentence now names both moves.
* The device-fragmentation section rested on two 2018 Android share numbers,
followed by a sentence conceding they would be stale on arrival. The
structural claim survives without them.
* The iOS developer fee was dated with "for 10 years at the time of this
writing".
* The UDID instructions walked the reader through iTunes, with a screenshot of
iTunes on iOS 9.3.5 that also exposed a real device serial and UDID. iTunes
has not existed on macOS since Catalina, and the signing chapter already gives
the current answer, so this now points there.
* The device build was "a right click away" via the IDE plugin, illustrated by a
menu still offering Blackberry, J2ME and Windows Phone builds. Replaced with
the `cn1:buildAndroid` and `cn1:buildIos` goals, taken from the mojo names.
Both screenshots are deleted, so find_unused_images.py stays satisfied.
Verified: asciidoctor at --failure-level WARN, Vale at suggestion level,
LanguageTool (status ok, 0 matches, run under JDK 17 rather than the JDK 8
false green), paragraph capitalization, snippets, xrefs, structure, links and
missing-code-blocks all clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* "-pl common" made a device build a no-op that reported success
Review caught the command I had just written. AbstractBuildWrapperMojo.execute()
opens with:
if (!project.isExecutionRoot()) {
getLog().info("Skipping execution for non-root project");
return;
}
and BuildAndroidMojo, BuildIosMojo and BuildWin32Mojo all inherit it unchanged.
Under `-pl common` the selected module is not the execution root, so the goal
logs that line and stops. The design is deliberate -- the wrapper re-invokes
Maven on the root pom itself (`request.setPomFile(new File("pom.xml"))`) -- so
naming a module both skips the wrapper and defeats its purpose.
Measured rather than reasoned, against scripts/hellocodenameone:
mvn -pl common ...:buildWin32 -> "Skipping execution for non-root project"
BUILD SUCCESS, nothing built
mvn ...:buildWin32 -> no skip line, proceeds into the reactor
A silent success is the worst failure mode for a getting-started instruction, so
the Introduction now says where to run it and warns about the module form
explicitly.
Working-With-Windows carried the same `-pl common` shape and is fixed with it --
it is where I copied the form from, so leaving it would reintroduce the defect
the next time someone follows the pattern.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* The Windows build wrappers named a platform no profile answers to
Review checked the command this PR documents and found it still builds nothing.
The wrapper passes `codename1.platform` to its nested Maven run, and that
property is what activates the module profile in a generated project's root pom.
The profile is `win`, matching the value the win module itself declares:
<profile><id>win</id>
<activation><property>
<name>codename1.platform</name><value>win</value>
</property></activation>
<modules><module>win</module></modules>
BuildWin32Mojo and BuildWindowsDeviceMojo both passed "windows", which matches no
profile at all. Nothing else in the plugin reads the platform as "windows", so
the value was simply inert: the win module never joined the reactor and the
nested build reported success having produced no Windows binary. Every other
wrapper already agrees with its profile -- android, ios, javascript, linux and
javase all match -- so these two were the only ones out of step.
Verified by A/B against scripts/hellocodenameone, reading the NESTED reactor
rather than the outer one:
before hellocodenameone, -common, -javase <- no win module
after hellocodenameone, -common, -win
The build TARGET is a separate namespace and stays "windows-device"
(Executor.BUILD_TARGET_WINDOWS_NATIVE), which is correct.
SpotBugs over codenameone-maven-plugin regenerated: 0 findings.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* The primary Win32 command had both defects the convenience goal just lost
Fixing BuildWin32Mojo made the sentence beside this snippet false: the goal now
submits a build, while the command the chapter leads with still could not. It
carried both defects at once --
mvn -pl common package -Dcodename1.platform=windows ...
-- `-pl common` builds that module instead of the reactor, so the win module is
never reached, and `windows` matches no profile even when the reactor is whole.
The two mistakes hid each other: with only the module selected, the platform
value had nothing left to activate.
Corrected to what the wrapper actually runs, read off AbstractBuildWrapperMojo:
goal `package`, `codename1.platform=win`, `codename1.buildTarget=windows-device`,
from the project root.
A/B against scripts/hellocodenameone, reading the reactor, which Maven prints
before it compiles:
old no win module in the reactor at all
new hellocodenameone, -common, -win
Both invocations then fail identically on this machine with "invalid target
release: 17", because tools/env.sh pins JDK 8 while the demo targets 17. That is
environmental and equal on both sides, so it does not affect the comparison.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@shai-almog
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Gates for the developer guide defects Asciidoctor cannot see - #5669

Open
shai-almog wants to merge 17 commits into
masterfrom
devguide-guards
Open

Gates for the developer guide defects Asciidoctor cannot see#5669
shai-almog wants to merge 17 commits into
masterfrom
devguide-guards

Conversation

@shai-almog

Copy link
Copy Markdown
Collaborator

Split out of #5664 so the structural repairs there stop waiting on tooling review. Stacked on it, because these checks are written against a guide that #5664 fixes — run them on master and several fail by design.

Four checks. Each was verified to fail when the defect it describes is reintroduced, and to pass again when it is reverted.

CheckCatchesAsciidoctor's behaviour
check-guide-structure.pya document in the tree but not in the book; a chapter that silently becomes a part, a subsection, or a duplicate; an included chapter whose title does not survive into the rendered outputsilent
check-guide-xrefs.pyan internal link that resolves to nothing, and one that renders as a bare [some-id]silent — a reference with link text renders as an ordinary link to nowhere
check-missing-code-blocks.pyprose that promises a listing where a hole followsnot its job
check-guide-links.pylinks checked against the paths the site actually servesnot its job

How the link checker decides

It does not carry a list of good links. It derives what the site serves and follows the rules to what they produce:

  • _redirects sources compiled to patterns, splat and :placeholder included, then followed to their destination rather than accepted for matching. /*.html matches every root-level .html path there is, so a match proves nothing.
  • Hugo routes from front matter — url, slug, aliases — because 1055 of the 1262 content pages override their route with slug, and deriving it from the filename both invents routes and rejects real ones.
  • Draft and future-dated pages excluded, since Hugo defaults buildDrafts and buildFuture to false.
  • Static assets enumerated as files; a directory without an index.html is not a route.
  • /developer-guide/ derived from what scripts/website/build.sh rsyncs. Only /javadoc/ stays exempt, because it is produced from the framework sources and cannot be enumerated here.

Ratchets

check-missing-code-blocks.py records 406 existing holes and check-guide-links.py38 bad links. Both are multisets — one line per occurrence, because a file naming the same bad URL twice has two problems.

Both may only shrink. A fixed entry that is still baselined fails, so the ratchet actually banks its fixes; growing one requires --allow-new, so recording new debt is deliberate rather than a side effect of regenerating.

Where they run

developer-guide-docs.yml runs all four. check-guide-links.py also runs from website-docs.yml, which triggers on docs/website/** — a page renamed or re-slugged there breaks the guide, and putting those paths into the guide workflow would run the whole guide build, maven install and screenshot generation included, for every blog post the daily publisher pushes.

The docs paths filter now covers scripts/developer-guide/** rather than two named files, because a change to a script not in that filter started the workflow while skipping the steps that script governs.

@chatgpt-codex-connector

chatgpt-codex-connectorBot commented Sep 2, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

ReviewStatusCommitReview trigger
📝 Code Review🔄 Running since 2026-09-02T15:38:41.729150Za9c0b73New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:2da41dd8c7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-links.py Outdated
Comment threadscripts/developer-guide/check-guide-structure.py

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:28f950b311

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-links.py

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:21e513112f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-links.py Outdated
@github-actions

Copy link
Copy Markdown
Contributor

Cloudflare Preview

@github-actions

github-actionsBot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Developer Guide build artifacts are available for download from this workflow run:

Developer Guide quality checks:

  • AsciiDoc linter: No issues found (report)
  • Vale: No alerts found (report)
  • Paragraph capitalization: No paragraph capitalization issues (report)
  • LanguageTool: No grammar matches (report)
  • Image references: No unused images detected (report)

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:6d0560cc05

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-structure.py
Comment threadscripts/developer-guide/check-guide-links.py

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:b96d1321a1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-links.py Outdated
Comment threadscripts/developer-guide/check-guide-links.py
@shai-almog
shai-almogforce-pushed the devguide-structure-and-guards branch from 6108cad to e113f04CompareSeptember 2, 2026 11:57

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:d82c0ea525

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-structure.py

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:42189c33d9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-links.py
Comment threadscripts/developer-guide/check-guide-links.py

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:89e588ddc8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-missing-code-blocks.py Outdated
Comment threadscripts/developer-guide/check-guide-xrefs.py Outdated

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:b88150d821

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-xrefs.py
Comment threadscripts/developer-guide/check-guide-structure.py Outdated
Base automatically changed from devguide-structure-and-guards to masterSeptember 2, 2026 13:19
shai-almogand others added 10 commits September 2, 2026 16:20
Four checks, split out of the structural repairs they were written alongside so
each can be reviewed on its own terms. Every one was verified to fail when the
defect it describes is reintroduced, and to pass again when it is reverted.
check-guide-structure.py -- every document is in the book or declared out of it;
no chapter silently becomes a part, a subsection, or a duplicate; and, by
rendering the book and reading its headings back, every included chapter's title
survives into the output. That last check is what would have caught a whole
chapter rendering as subsections of the one before it.
check-guide-xrefs.py -- every internal link resolves, and none renders as a bare
"[some-id]". Asciidoctor reports neither: a reference carrying link text renders
as an ordinary link to nowhere.
check-missing-code-blocks.py -- prose that promises a listing where a hole
follows. 406 exist, from the snippet extraction in bbdc605, recoverable from
bbdc605~1.
check-guide-links.py -- links checked against the paths the site actually
serves, derived from _redirects and the Hugo content tree and followed through
every redirect rule to what it produces, rather than accepted for matching one.
The two ratchets may only shrink; growing one needs --allow-new, so recording
debt is deliberate rather than a side effect of regenerating.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The stacked content change restored the video capture constraints examples, so
their baseline entries went stale and the ratchet refused to pass until they
were removed. 406 to 402.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The outcome check compared counts of normalized headings at any depth, which a
collision satisfies: "Analytics" is a chapter and also a subsection of Commerce,
so the rendered book contains it twice. If the chapter were swallowed, the
subsection kept the count at one and the check -- the central safeguard of this
change -- passed. Measured on the current book: analytics appears twice at any
level and once at chapter level; "getting started" four times at any level and
never at chapter level.
Manifest entries are now counted against chapter-level headings only. Nested
fragments still use the any-level count, because their depth is whatever their
parent gives them.
Second finding: resolves() tried every matching redirect rule and passed if any
chain worked. The host applies the FIRST match and stops, so a link matching an
early rule that leads somewhere deleted would have passed on the strength of a
later rule the reader never reaches. It now returns the first match's result. No
guide link changes verdict today; the point is that the model matches the
behaviour it claims to model.
Also declined, with the reasoning left in the code rather than a review thread:
adding Markdown's three-backtick fence to the literal-block list. The guide
contains none, and validate-guide-snippets.py requires every listing to be
[source,LANG] with a bare include:: inside ---- delimiters, so such a block
would fail that gate before reaching this one.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The pattern required empty brackets, which is right for ifdef and ifndef -- with
content they are the single-line form and guard only that line -- but wrong for
ifeval, which has no single-line form and always carries its expression in the
brackets. So the directive was listed and never recognised.
Latent: the manifest contains no ifeval, and its only conditionals are two
single-line ifdef attribute assignments, which are still correctly treated as
not opening a block. Fixed because a pattern that names three directives and
handles two is a bug in what the code claims, not a missing feature.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review asked for AsciiDoc comment-state tracking so that a URL inside a `//`
line or a `////` block is skipped. Declining, and recording it where the next
reader of this loop will look rather than in a review thread nobody reads.
The guide has 120 files, zero commented-out URLs and zero `////` blocks, so the
tracking would govern nothing that exists. It would also weaken the gate: with
comments skipped, commenting a line out makes its finding disappear and lets the
ratchet shrink, banking a "fix" while the dead link stays in the source waiting
to be uncommented. Deleting the link is the fix.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review asked for fragment validation on same-site URLs. Measuring first: the
guide holds 42 same-site URLs with a fragment, and 38 are /javadoc/ -- the one
tree this script cannot enumerate, because it is generated from the framework
sources at build time. The other four all pointed at
`developer-guide.html#<anchor>`: this book, linking into its own body.
Those are the same defect as the fifteen `/manual/*.html` deep links already
converted in this stack, missed because they use a different path. They send a
reader out to the web to fetch the page they are reading, they do not work in
the PDF, and a renamed section breaks them silently -- the anchor lives in the
rendered book, so no amount of site-path checking can see it.
So the fix is not to validate the fragment, it is to stop writing the link:
all four become `<<anchor,text>>` xrefs, which check-guide-xrefs.py already
resolves against the rendered anchors. All three targets exist today, so this
repairs no live 404 -- it moves four unguarded links under a gate.
The rule closes the class rather than the four instances: any codenameone.com
link whose path is one of this book's own routes and which carries a fragment is
now reported. Verified non-vacuous by restoring one of the four and watching it
fail, then reverting.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both findings describe constructs the guide does not contain, so neither is a
live defect. Both are real holes in the checkers though, and each closes for a
few lines, so they close rather than getting argued with.
Conditional includes (check-guide-structure.py). A manifest entry inside an
ifdef/ifeval was skipped by the duplicate count -- correctly, since the same
chapter under two exclusive branches is one chapter in the output -- and also by
the rendered-title check, since only one branch renders. Skipping both means a
chapter included twice inside a single ACTIVE branch would pass silently. Rather
than teach the checker to distinguish exclusive branches for a construct that
does not exist (measured: zero include:: lines sit inside a conditional anywhere
in the guide), a conditional manifest entry is now refused outright, with a
message saying what to extend if one is ever wanted.
Trailing slashes on file paths (check-guide-links.py). Site paths were compared
with the trailing slash stripped, so a link to `/x.jar/` was validated against
`/x.jar`. The reviewer's premise checks out: _redirects declares the two forms as
separate routes and spells both out where both work, 32 such pairs. Preserving
the slash everywhere would break the many legitimate directory links, so the
narrow case is reported instead -- a last path segment containing a dot, wearing
a trailing slash. Directory routes such as /blog/ and /javadoc/com/codename1/io/
have no dot in the last segment and are untouched.
Both rules verified non-vacuous by introducing an instance and reading the real
exit status rather than a pipeline's: structure and links each exit 1 on the
probe and 0 once it is reverted.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review is right that the rendered-title check has slack: it counts normalized
titles across the whole book, so a nested fragment losing its heading can be
covered by an identical heading elsewhere. Confirmed rather than accepted --
removing the leveloffset from both Maven includes takes "Getting started" from 4
rendered headings to 3, while only one file declares it, so `3 >= 1` and the gate
passed a genuinely swallowed section.
Rather than give nested fragments a rendered identity, which the HTML does not
carry, this checks the cause. The spacing rule already existed but ran only on
the root manifest, and its condition was "a blank line follows". Both were wrong.
Reproduced minimally to find the real rule -- a heading is absorbed only when ALL
of these hold:
* the include is followed immediately by content, and
* neither this include nor the following one carries leveloffset (asciidoctor
brackets the content with :leveloffset: attribute entries, and an attribute
entry closes the paragraph -- the one on the FOLLOWING include lands between
the paragraph and the heading, so it protects just as well), and
* the included file does not end on a blank line, and
* its last line is ordinary paragraph text -- a delimiter, table row, heading,
attribute entry or comment all close the paragraph. That is why
_generated-build-hints.adoc, which ends on "|===", does not eat the
"Versioned builds" heading directly after it.
Against the real book, all four combinations now agree with what asciidoctor
actually renders:
both leveloffset 4 headings renders ok gate passes
only current 4 headings renders ok gate passes
only following 4 headings renders ok gate passes
neither 3 headings SWALLOWED gate fails
An earlier draft exempted only the current include and failed the third row --
a false positive on markup that renders correctly. Every guide include is now
checked, not just the manifest's: measured across all 120 files, this leaves zero
findings, because the five adjacent includes in Maven-Project-Workflow.asciidoc
are protected by leveloffset.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… the rest
Three link findings, and only the first was a live defect -- in the rule I added
one commit earlier.
The trailing-slash rule was unconditional, so it reported "/videos.html/" as
broken even though _redirects declares that exact source and the site serves it.
The rules here all compile slash-insensitively ("^...$/?"), which is right for
matching but discards the distinction the site actually draws, so the raw sources
are now collected alongside them and a slashed file path is reported only when
_redirects does not spell it out. Probed both ways: "/videos.html/" passes,
"/files/CodenameOneBuildClient.jar/" fails.
Hugo route overrides. hugo.toml sets neither [permalinks] nor uglyURLs today, so
deriving a route as "section path + slug" is currently correct; if either
appears, every route underneath moves and this would go on accepting links to
paths Hugo no longer publishes. Modelling a configuration that is not there would
be guesswork, and reading the built public/ tree makes a local run depend on a
tree that may be stale or absent. So it now notices instead: either key present
aborts the check with what to do about it. Probed with each key in turn.
Protocol-relative links and fragments on ordinary same-site pages are declined,
with the measurements recorded in the code rather than in a review thread nobody
reads. The guide has no `link://` macro at all, and its one bare "//host/path" is
a JavaScript string inside a source block, so widening URL_RE to match "//" would
start reporting code as a broken link. And of the 42 same-site URLs carrying a
fragment, 38 are /javadoc/ -- generated at build time, exempt for that reason --
while the other four pointed into this book and are now xrefs, which
check-guide-xrefs.py resolves against the rendered anchors. Nothing is left that
this script could check without building the Hugo site.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ting
Two more holes in the guards. Neither has an instance in the guide today, and
both close cheaply enough that arguing about them would cost more.
check-guide-xrefs.py rendered the book once, with the default backend, so
anything inside ifdef::backend-pdf[] was dropped before a single reference in it
could be examined -- while the workflow publishes an asciidoctor-pdf build from
the same source. Native-Themes.asciidoc already has two such branches. It now
renders twice, the second time with -a backend-pdf, which selects exactly the
content the PDF build includes without needing asciidoctor-pdf here. The two runs
are checked SEPARATELY rather than pooled: an anchor that exists only in the HTML
branch must not satisfy a reference made in the PDF branch. Findings name the
branch they came from.
A/B with a dangling xref planted inside the backend-pdf branch of
Native-Themes.asciidoc:
old "Cross-references OK: 1741 anchors" -- exit 0
new "Native-Themes.asciidoc:506: <<definitely-not-a-real-anchor>> ...
(1 reference(s), pdf render)" -- exit 1
check-missing-code-blocks.py accepted any `[attribute]` line as the start of the
promised block, so an admonition standing where a listing used to be hid the
hole. An admonition is prose and can never be the listing a sentence promised, so
the five names are excluded. Excluded by name rather than by whitelisting the
kinds that ARE code: measured, the bracket lines legitimately answering a
promising sentence already span [source] (582), [cols=...] and [options=...] (56),
[listing] (3), [quote] (2) and an anchored image, and a whitelist would report the
next kind nobody anticipated.
A/B with each block planted after a promising sentence, using the real signature
of a removed listing -- the two blank lines it leaves behind:
[NOTE] old: none new new: reported as a hole
[source,java] old: none new new: none new
The baseline is unchanged at 402, because the guide currently has no admonition
sitting in a listing's place.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…separator
Two findings, and this time the first one is live.
The rendered book contains exactly three relative hrefs, and all three are
broken: css.asciidoc carried link:Images[], link:Fonts[] and
link:Supported-Properties#text-decoration[] over from the wiki this guide
replaced, naming pages that were never carried across with them. The guide
renders as a single page, so a relative href resolves against wherever that page
is served and reaches nothing that ships with it -- the reader gets a 404 while
every gate reported success. All three targets are sections in that same file:
[[Images]] already existed, [[Fonts]] is added to match it, and
[[text-decoration]] was already there and already used by a sibling reference.
check-guide-xrefs.py now reports any relative href, which after this change has
zero instances. Absolute URLs stay with check-guide-links.py, and root-relative
paths and fragments are excluded. A/B with one link restored: old printed
"Cross-references OK" and exited 0, new reports it and exits 1.
The second is a wrong claim in my own comment. check-guide-structure.py returned
early when an include was followed by ifdef/ifndef/ifeval/endif, on the reasoning
that "a preprocessor directive is not content and cannot absorb a paragraph".
True, and irrelevant: asciidoctor REMOVES the directive during preprocessing, so
it does not separate anything either. Measured -- with `ifdef::backend-html5[]`
between an include ending in prose and a following heading, the render contains
zero heading tags and the paragraph reads "...preferred tools. === A parent-local
heading". The rule now scans past directive lines to the first line that survives
preprocessing. A/B on that construct: old passed it, new reports it.
Note the rendered-title check cannot cover this case either, because a
parent-local heading is not the first heading of any included file.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:f9e50cb8cc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-links.py
Comment threadscripts/developer-guide/check-guide-xrefs.py
… has a limit
The first finding was live: the link ratchet held a working link.
docs/website/functions/[[path]].js runs after context.next() has already 404ed
and then redirects anything under /files/ or /demos/ to
download.codenameone.com. Those paths are served, so the model was missing a
whole class of route and had banked a real one -- /files/iOS_UI-Kit.psd -- as
broken. The destination is off-site, which puts it in the same bucket as every
other off-site redirect: reachable, not verifiable from this repository. The
rules are appended AFTER the _redirects rules because the function is a fallback
and the first matching rule wins, mirroring how the host evaluates. The baseline
shrinks 37 -> 36, and a new /files/ link is now accepted rather than failing CI.
The second is a real limit of the PDF surrogate, and one the command line cannot
remove. Setting backend-pdf makes ifndef::backend-pdf[] content disappear and
ifdef::backend-pdf[] content appear -- verified, and that is the only form this
guide uses. It does not undefine backend-html5: the HTML converter sets that
itself, after command-line attributes are applied, so even `-a backend-html5!`
leaves it defined. Measured both ways. Content guarded on the HTML backend would
therefore survive into the surrogate render and could satisfy a PDF-only
reference that the real asciidoctor-pdf build leaves dangling.
The guide has no such conditional, so rather than model a construct that is not
there -- or drive asciidoctor-pdf and try to read anchors back out of a PDF --
the checker refuses one if it appears, naming the alternative the rest of the
guide already uses. Probed: planting an ifdef::backend-html5[] block aborts the
check with that message.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:6a8963d02a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-structure.py Outdated
…side one
Both scanners matched a delimiter of exactly four characters, so a block opened
with ----- was invisible to them and its contents were scanned as live markup.
The guide has 20 such lines -- eight in Desktop-Integration, six in
Working-With-iOS, six in appendix_goal_generate_archetype -- every one an
ordinary [source] block wrapping an include::. validate-guide-snippets.py already
accepted -{4,}; these two had drifted from it.
Nothing is misread today, because those includes name .java, .xml and .properties
files and the reachability walk skips non-asciidoc targets. The failure mode is
still the interesting one though: it is a FALSE POSITIVE generator, not a missed
defect. A/B with each construct planted inside a five-dash block:
a displayed include::Working-with-UWP.asciidoc[]
old reported a swallowed-heading error against markup that only appears
on the page as text
new clean
a displayed "The wrapper looks like this:" followed by blank lines
old reported it as a missing code block
new clean
Both now track the delimiter that opened the block and close on one of the same
character AND length, which is what asciidoctor does -- so a four-dash line
inside a five-dash block is content rather than the close. The set stays limited
to literal blocks: an example (====) or sidebar (****) block contains live
markup, so headings and includes inside one are real and must keep counting.
Baselines unchanged: 119 included documents, 402 holes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:57212b7f43

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-structure.py
Comment threadscripts/developer-guide/check-guide-xrefs.py
shai-almogand others added 2 commits September 2, 2026 18:21
* Guide figures render with a bundled font instead of the host's
None of the 24 generated figures could be reproduced outside CI. Running the
generator on a Mac reproduces 0 of 24 byte-for-byte, with 3-20% of pixels
differing. Two consecutive local runs are byte-identical, so the generator is
deterministic on one host; the variable is the font.
`PreAdvancedThemingScreenshots` styled with `Font.createSystemFont`, which
resolves through `JavaSEPort.fontFaceSystem` -- "Arial" on macOS and Linux
alike. Arial exists on a developer's Mac and not on a stock CI runner, so AWT
silently substitutes and every glyph changes. The differences are exactly that:
for `flow-layout.png` every differing pixel sits in y 10-130, the title and
label rows, while the colored blocks below match to the pixel.
So the byte-exact gate has only ever been green because CI both generates and
verifies. A developer regenerating locally could not match it, and at the scale
the guide's remaining ~260 app screenshots would need, that is untenable.
The figures now load `native:MainRegular`, which `JavaSEPort.loadTrueTypeFont`
reads from `/com/codename1/impl/javase/Roboto-Medium.ttf` on the classpath
rather than from an installed-font lookup. This is also what the project's font
rule requires everywhere: never `createSystemFont`, always the `native:` scheme.
A null return refuses loudly rather than falling back to a host font, because a
silent fallback would restore the exact non-determinism this removes.
The committed baselines are regenerated from a Mac. CI byte-compares them on
Linux, so the check either passes -- proving host independence rather than
asserting it -- or fails and says so immediately.
Composition, dimensions and colours are unchanged; only the typeface moves.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Pin the font on every component, not just the ones that get block colours
CI reported one stale figure out of 24: `guibuilder-2-insets-3.png`, the only
one containing a `TextField`. The other 23 matched Linux byte for byte, so the
bundled-font change worked -- it just did not reach far enough.
`applyBlockStyleToContent` styled `Label` and `Button`. Everything else kept the
theme's default font, which resolves through the host, so the text field and its
hint were still host-dependent. Enumerating the types that carry text would have
left the next one added broken the same way, so the walk now pins the face on
every component it visits, plus the hint label, which is painted by a `Label`
that is not in the component tree and so is never reached by the walk.
The first attempt used `BLOCK_FONT` for this and regressed the figure: at 29px
against the theme default's 13px the field grew and squeezed "Submit" down to a
clipped sliver. `FIELD_FONT` is sized to reproduce the original height, so the
composition is unchanged and only the typeface moves.
Verified locally by rendering twice, once with `JavaSEPort.setFontFaces` pointed
at a family that does not exist -- which is what a machine without Arial looks
like to the port. All 24 come out byte-identical, so nothing in these figures
reads an installed font any more.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Publish what the runner actually rendered when a screenshot fails
"Committed screenshot is stale: <name>" names the file and nothing else, so
there is no way to tell a real regression from an environment difference
without adding a debugging round trip to CI. The generated directory is now
uploaded as an artifact when the step fails.
Needed immediately: one figure still differs between a Mac and the runner after
the font fix, and host fonts, JDK version and working directory have each been
ruled out locally.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Pin MigLayout's platform, and stop demanding byte equality of a glyph edge
The artifact step paid for itself immediately. Downloading what the runner
rendered showed two figures differing, not the one the gate reported -- it exits
on the first mismatch -- and the two had nothing in common.
`mig-layout.png` differed by 12.8% of its pixels, a real layout shift.
MigLayout takes its default gaps from `PlatformDefaults`, which reads
`System.getProperty("os.name")` and answers MAC_OSX, GNOME or WINDOWS_XP, each
with different spacing. The figure was rendering with macOS gaps on a Mac and
GNOME gaps on the runner. Pinning the platform fixes it: with the pin, 23 of the
24 figures now match the runner's own output byte for byte.
`layered-layout.png` differed by 173 pixels, 0.113%, inside a 25x25 box. That
one is not fixable. Measured against the runner's bytes, the material glyph
lands at exactly the same size and the same origin -- a 55x49 bounding box --
and differs only in antialiased edge coverage, 946 fully-white pixels against
916. Java2D rasterizes the same glyph, from the same bundled font, at the same
size, slightly differently on the two platforms. Demanding byte equality there
would mean deleting legitimate content from the figure or carrying a
permanently red check.
So the comparison moves from `cmp -s` to a comparer that still requires byte
equality by default and accepts a bounded difference only where a figure carries
a `.tolerance` sidecar explaining itself, in the same key=value shape the CN1SS
suites already use. The area bound does the work: a per-pixel delta that large is
meaningless on its own, since a glyph edge flips between white and the block
behind it, but a regression that changed the icon would move far more than 0.3%
of the image.
Verified by running the comparer with the runner's own output against the
committed figures -- which is exactly what CI will do -- and by four probes: an
untoleranced figure that differs fails, the toleranced figure fails when changed
beyond its budget, a missing figure fails the count, and a clean run passes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Guard the iOS font branch, and stop the comparator gating itself out
Two review findings on this PR.
The first says `native:MainRegular` does not give host-independent output,
because `loadTrueTypeFont` resolves native fonts to the first installed SF or
Helvetica family before reaching the bundled Roboto. The branch is real, but it
is reached only when `isIOS` is set, which `loadSkinFile` does for a skin whose
systemFontFamily contains "helvetica" -- and this generator never loads a skin.
The measurement agrees: figures rendered on a Mac match the Linux runner byte
for byte, which could not happen if one side were resolving Helvetica Neue and
the other Roboto.
So the conclusion does not hold today, but the risk is real for tomorrow: a
change that loads a skin here would put host fonts back into the output with no
other symptom. The generator now refuses to run under an iOS platform, and says
why, rather than leaving that to a comment nobody reads.
The second finding is straightforwardly right and is the more serious of the
two. `on.pull_request.paths` triggers on `scripts/developer-guide/**`, but the
`Determine changed components` filter named only two scripts, so a pull request
touching any other script here started the workflow with `docs` false and
skipped the steps that script governs. A change to compare-screenshots.py could
have merged without the screenshot check ever running it -- a gate that skips
itself. The filter now covers the whole directory, which also removes the
two-copies-drift the surrounding comment already warns about.
Verified the guard changes no output: all 24 figures are unchanged, and the
comparer still passes against the runner's own bytes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Count every changed pixel toward the area budget, not just the loud ones
The comparator incremented its mismatch counter only for pixels whose channel
delta EXCEEDED maxChannelDelta, which is what the CN1SS comparator does. With a
sidecar written the way this one was -- a large delta bound paired with a small
area bound -- that leaves an unbounded hole, and review gave the exploit in the
figures' own palette: recolouring the green #06a806 to #a608a6 moves every
channel by exactly 160, so with maxChannelDelta=160 not one pixel is counted and
a dramatically different image reports zero mismatches.
Reproduced it before fixing: the recolour changes 4191 pixels, 2.73% of the
image, at a worst delta of exactly 160 -- and passed.
The two bounds are now independent. maxMismatchPercent limits how much of the
image may change at all, counting every differing pixel; maxChannelDelta caps how
far any single pixel may move. The measured legitimate noise -- 173 pixels,
0.113%, worst delta 141 -- still passes, the recolour now fails on area, and a
five-pixel solid overwrite fails on delta.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Correct the sidecar's rationale to match how the bounds now work
The previous commit changed the comparator so every differing pixel counts
toward the area budget and the channel delta is a separate ceiling. The sidecar
still explained the old behaviour -- that the area bound did the work and the
delta was meaningless on its own -- which is now wrong in a file whose whole
purpose is to justify the numbers beside it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Compare figures in RGBA, because they have an alpha channel
The comparator converted both images to RGB before counting differences. The
committed figures are genuine RGBA PNGs -- four channels, currently opaque
everywhere -- so that dropped a real channel, and any change confined to alpha
was invisible to the tolerance path. A regression that turned the whole figure
transparent while leaving every colour channel intact reported zero changed
pixels and passed.
Byte equality, which every figure without a sidecar is still held to, always
caught this. Only a figure carrying a sidecar could reach the weakened path, so
today the exposure was one image -- but that image is exactly the one whose
comparison is relaxed.
Verified: making layered-layout.png fully transparent with its RGB channels
untouched now reports 100% of pixels changed, the real runner output still
passes, and the recolour and untoleranced-difference probes still fail as they
did.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…page
Two findings, one of them a false positive I introduced last round.
Comment blocks. Asciidoctor drops a //// block entirely, so an include inside
one does not put its target in the book and prose inside one promises the reader
nothing -- but both scanners read straight through. //// joins the fence set in
each. A/B with each construct planted inside a comment block: a displayed
include:: made the structure check fail, and a promising sentence made the
missing-block check fail; both are clean now.
Note this is deliberately NOT symmetric with check-guide-links.py, which still
reads URLs inside comments. The reasons differ and the code says so: a dead link
commented out is still dead debt in the source, and letting the ratchet shrink
for it would make "comment the line out" a way to silence that gate. Reachability
is the opposite -- calling a commented-out include reachable states something
about the book that is simply untrue.
Packaged assets. The relative-href rule added last round was too broad: the HTML
packaging step copies every subdirectory of docs/developer-guide next to
developer-guide.html, so link:img/example.png[] resolves for a reader who opens
the zip, and the gate would have rejected it. A relative href that names an
existing file under the guide directory is now allowed. sketch/ is not, because
that is the one directory the packaging loop skips.
Probed all four ways, since an exemption is only worth having if it still
rejects:
link:img/flow-layout.png[] exit 0 (real, packaged)
link:img/does-not-exist.png[] exit 1
link:sketch/whatever.svg[] exit 1 (not packaged)
link:Supported-Properties[] exit 1 (the wiki-era shape)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:a6bd5197dc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-xrefs.py Outdated
Comment threadscripts/developer-guide/check-guide-xrefs.py
shai-almogand others added 2 commits September 2, 2026 18:35
Both findings are defects in the two rules added in the previous commit.
packaged_asset() asked whether the file exists here, which is not the question. A
link only works if the published output carries it, and the two outputs filter
differently:
* the HTML archive copies only the SUBDIRECTORIES of docs/developer-guide next
to developer-guide.html and skips sketch, so a root-level file is never in the
zip however much it exists in the tree;
* the website rsync excludes sketch/, *.asciidoc and *.adoc, so a source file
nested inside a packaged directory still does not reach the site.
So link:Introduction.asciidoc[] was being exempted and readers would follow it to
nothing. Both filters are now reproduced. The permissive direction is the
expensive one here -- it suppresses a finding for a link nobody can follow --
so all four cases are probed:
img/flow-layout.png exit 0 in both outputs
Introduction.asciidoc exit 1 in neither
img/does-not-exist.png exit 1
sketch/foo.svg exit 1 excluded by both
The backend-conditional rejection scanned raw lines, so a source block DISPLAYING
`ifdef::backend-html5[]` as example text aborted the whole gate. A displayed
directive takes no part in preprocessing. It now tracks literal and comment block
delimiters the way the other guide scanners do. A/B: displayed inside a source
block went 1 -> 0, while a genuinely active conditional still exits 1.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…18 statistics (#5671)
* The Introduction taught a dead IDE menu, an iTunes walkthrough and 2018 statistics
Six corrections to the chapter a new developer reads first:
* The JavaScript target was described twice and contradicted itself: the
overview said TeaVM did the translation, the port section said ParparVM with
TeaVM as a fallback. The port section is right -- `javascript.port` is
declared with `parparvm` as its default and `teavm` documented as "the
original builder as a compatibility fallback".
* `http://teavm.org:[TeaVM-based builder]` -- the stray colon made the macro a
bare host, and the site serves TLS. Removed from the link ratchet, which now
stands at 37.
* Bitcode was cited as something ParparVM absorbed without modification. Apple
has since withdrawn bitcode, which makes the point better than the arrival
did, so the sentence now names both moves.
* The device-fragmentation section rested on two 2018 Android share numbers,
followed by a sentence conceding they would be stale on arrival. The
structural claim survives without them.
* The iOS developer fee was dated with "for 10 years at the time of this
writing".
* The UDID instructions walked the reader through iTunes, with a screenshot of
iTunes on iOS 9.3.5 that also exposed a real device serial and UDID. iTunes
has not existed on macOS since Catalina, and the signing chapter already gives
the current answer, so this now points there.
* The device build was "a right click away" via the IDE plugin, illustrated by a
menu still offering Blackberry, J2ME and Windows Phone builds. Replaced with
the `cn1:buildAndroid` and `cn1:buildIos` goals, taken from the mojo names.
Both screenshots are deleted, so find_unused_images.py stays satisfied.
Verified: asciidoctor at --failure-level WARN, Vale at suggestion level,
LanguageTool (status ok, 0 matches, run under JDK 17 rather than the JDK 8
false green), paragraph capitalization, snippets, xrefs, structure, links and
missing-code-blocks all clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* "-pl common" made a device build a no-op that reported success
Review caught the command I had just written. AbstractBuildWrapperMojo.execute()
opens with:
if (!project.isExecutionRoot()) {
getLog().info("Skipping execution for non-root project");
return;
}
and BuildAndroidMojo, BuildIosMojo and BuildWin32Mojo all inherit it unchanged.
Under `-pl common` the selected module is not the execution root, so the goal
logs that line and stops. The design is deliberate -- the wrapper re-invokes
Maven on the root pom itself (`request.setPomFile(new File("pom.xml"))`) -- so
naming a module both skips the wrapper and defeats its purpose.
Measured rather than reasoned, against scripts/hellocodenameone:
mvn -pl common ...:buildWin32 -> "Skipping execution for non-root project"
BUILD SUCCESS, nothing built
mvn ...:buildWin32 -> no skip line, proceeds into the reactor
A silent success is the worst failure mode for a getting-started instruction, so
the Introduction now says where to run it and warns about the module form
explicitly.
Working-With-Windows carried the same `-pl common` shape and is fixed with it --
it is where I copied the form from, so leaving it would reintroduce the defect
the next time someone follows the pattern.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* The Windows build wrappers named a platform no profile answers to
Review checked the command this PR documents and found it still builds nothing.
The wrapper passes `codename1.platform` to its nested Maven run, and that
property is what activates the module profile in a generated project's root pom.
The profile is `win`, matching the value the win module itself declares:
<profile><id>win</id>
<activation><property>
<name>codename1.platform</name><value>win</value>
</property></activation>
<modules><module>win</module></modules>
BuildWin32Mojo and BuildWindowsDeviceMojo both passed "windows", which matches no
profile at all. Nothing else in the plugin reads the platform as "windows", so
the value was simply inert: the win module never joined the reactor and the
nested build reported success having produced no Windows binary. Every other
wrapper already agrees with its profile -- android, ios, javascript, linux and
javase all match -- so these two were the only ones out of step.
Verified by A/B against scripts/hellocodenameone, reading the NESTED reactor
rather than the outer one:
before hellocodenameone, -common, -javase <- no win module
after hellocodenameone, -common, -win
The build TARGET is a separate namespace and stays "windows-device"
(Executor.BUILD_TARGET_WINDOWS_NATIVE), which is correct.
SpotBugs over codenameone-maven-plugin regenerated: 0 findings.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* The primary Win32 command had both defects the convenience goal just lost
Fixing BuildWin32Mojo made the sentence beside this snippet false: the goal now
submits a build, while the command the chapter leads with still could not. It
carried both defects at once --
mvn -pl common package -Dcodename1.platform=windows ...
-- `-pl common` builds that module instead of the reactor, so the win module is
never reached, and `windows` matches no profile even when the reactor is whole.
The two mistakes hid each other: with only the module selected, the platform
value had nothing left to activate.
Corrected to what the wrapper actually runs, read off AbstractBuildWrapperMojo:
goal `package`, `codename1.platform=win`, `codename1.buildTarget=windows-device`,
from the project root.
A/B against scripts/hellocodenameone, reading the reactor, which Maven prints
before it compiles:
old no win module in the reactor at all
new hellocodenameone, -common, -win
Both invocations then fail identically on this machine with "invalid target
release: 17", because tools/env.sh pins JDK 8 while the demo targets 17. That is
environmental and equal on both sides, so it does not affect the comparison.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@shai-almog
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Gates for the developer guide defects Asciidoctor cannot see - #5669

Open
shai-almog wants to merge 17 commits into
masterfrom
devguide-guards
Open

Gates for the developer guide defects Asciidoctor cannot see#5669
shai-almog wants to merge 17 commits into
masterfrom
devguide-guards

Conversation

@shai-almog

Copy link
Copy Markdown
Collaborator

Split out of #5664 so the structural repairs there stop waiting on tooling review. Stacked on it, because these checks are written against a guide that #5664 fixes — run them on master and several fail by design.

Four checks. Each was verified to fail when the defect it describes is reintroduced, and to pass again when it is reverted.

CheckCatchesAsciidoctor's behaviour
check-guide-structure.pya document in the tree but not in the book; a chapter that silently becomes a part, a subsection, or a duplicate; an included chapter whose title does not survive into the rendered outputsilent
check-guide-xrefs.pyan internal link that resolves to nothing, and one that renders as a bare [some-id]silent — a reference with link text renders as an ordinary link to nowhere
check-missing-code-blocks.pyprose that promises a listing where a hole followsnot its job
check-guide-links.pylinks checked against the paths the site actually servesnot its job

How the link checker decides

It does not carry a list of good links. It derives what the site serves and follows the rules to what they produce:

  • _redirects sources compiled to patterns, splat and :placeholder included, then followed to their destination rather than accepted for matching. /*.html matches every root-level .html path there is, so a match proves nothing.
  • Hugo routes from front matter — url, slug, aliases — because 1055 of the 1262 content pages override their route with slug, and deriving it from the filename both invents routes and rejects real ones.
  • Draft and future-dated pages excluded, since Hugo defaults buildDrafts and buildFuture to false.
  • Static assets enumerated as files; a directory without an index.html is not a route.
  • /developer-guide/ derived from what scripts/website/build.sh rsyncs. Only /javadoc/ stays exempt, because it is produced from the framework sources and cannot be enumerated here.

Ratchets

check-missing-code-blocks.py records 406 existing holes and check-guide-links.py38 bad links. Both are multisets — one line per occurrence, because a file naming the same bad URL twice has two problems.

Both may only shrink. A fixed entry that is still baselined fails, so the ratchet actually banks its fixes; growing one requires --allow-new, so recording new debt is deliberate rather than a side effect of regenerating.

Where they run

developer-guide-docs.yml runs all four. check-guide-links.py also runs from website-docs.yml, which triggers on docs/website/** — a page renamed or re-slugged there breaks the guide, and putting those paths into the guide workflow would run the whole guide build, maven install and screenshot generation included, for every blog post the daily publisher pushes.

The docs paths filter now covers scripts/developer-guide/** rather than two named files, because a change to a script not in that filter started the workflow while skipping the steps that script governs.

@chatgpt-codex-connector

chatgpt-codex-connectorBot commented Sep 2, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

ReviewStatusCommitReview trigger
📝 Code Review🔄 Running since 2026-09-02T15:38:41.729150Za9c0b73New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:2da41dd8c7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-links.py Outdated
Comment threadscripts/developer-guide/check-guide-structure.py

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:28f950b311

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-links.py

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:21e513112f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-links.py Outdated
@github-actions

Copy link
Copy Markdown
Contributor

Cloudflare Preview

@github-actions

github-actionsBot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Developer Guide build artifacts are available for download from this workflow run:

Developer Guide quality checks:

  • AsciiDoc linter: No issues found (report)
  • Vale: No alerts found (report)
  • Paragraph capitalization: No paragraph capitalization issues (report)
  • LanguageTool: No grammar matches (report)
  • Image references: No unused images detected (report)

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:6d0560cc05

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-structure.py
Comment threadscripts/developer-guide/check-guide-links.py

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:b96d1321a1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-links.py Outdated
Comment threadscripts/developer-guide/check-guide-links.py
@shai-almog
shai-almogforce-pushed the devguide-structure-and-guards branch from 6108cad to e113f04CompareSeptember 2, 2026 11:57

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:d82c0ea525

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-structure.py

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:42189c33d9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-links.py
Comment threadscripts/developer-guide/check-guide-links.py

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:89e588ddc8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-missing-code-blocks.py Outdated
Comment threadscripts/developer-guide/check-guide-xrefs.py Outdated

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:b88150d821

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-xrefs.py
Comment threadscripts/developer-guide/check-guide-structure.py Outdated
Base automatically changed from devguide-structure-and-guards to masterSeptember 2, 2026 13:19
shai-almogand others added 10 commits September 2, 2026 16:20
Four checks, split out of the structural repairs they were written alongside so
each can be reviewed on its own terms. Every one was verified to fail when the
defect it describes is reintroduced, and to pass again when it is reverted.
check-guide-structure.py -- every document is in the book or declared out of it;
no chapter silently becomes a part, a subsection, or a duplicate; and, by
rendering the book and reading its headings back, every included chapter's title
survives into the output. That last check is what would have caught a whole
chapter rendering as subsections of the one before it.
check-guide-xrefs.py -- every internal link resolves, and none renders as a bare
"[some-id]". Asciidoctor reports neither: a reference carrying link text renders
as an ordinary link to nowhere.
check-missing-code-blocks.py -- prose that promises a listing where a hole
follows. 406 exist, from the snippet extraction in bbdc605, recoverable from
bbdc605~1.
check-guide-links.py -- links checked against the paths the site actually
serves, derived from _redirects and the Hugo content tree and followed through
every redirect rule to what it produces, rather than accepted for matching one.
The two ratchets may only shrink; growing one needs --allow-new, so recording
debt is deliberate rather than a side effect of regenerating.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The stacked content change restored the video capture constraints examples, so
their baseline entries went stale and the ratchet refused to pass until they
were removed. 406 to 402.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The outcome check compared counts of normalized headings at any depth, which a
collision satisfies: "Analytics" is a chapter and also a subsection of Commerce,
so the rendered book contains it twice. If the chapter were swallowed, the
subsection kept the count at one and the check -- the central safeguard of this
change -- passed. Measured on the current book: analytics appears twice at any
level and once at chapter level; "getting started" four times at any level and
never at chapter level.
Manifest entries are now counted against chapter-level headings only. Nested
fragments still use the any-level count, because their depth is whatever their
parent gives them.
Second finding: resolves() tried every matching redirect rule and passed if any
chain worked. The host applies the FIRST match and stops, so a link matching an
early rule that leads somewhere deleted would have passed on the strength of a
later rule the reader never reaches. It now returns the first match's result. No
guide link changes verdict today; the point is that the model matches the
behaviour it claims to model.
Also declined, with the reasoning left in the code rather than a review thread:
adding Markdown's three-backtick fence to the literal-block list. The guide
contains none, and validate-guide-snippets.py requires every listing to be
[source,LANG] with a bare include:: inside ---- delimiters, so such a block
would fail that gate before reaching this one.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The pattern required empty brackets, which is right for ifdef and ifndef -- with
content they are the single-line form and guard only that line -- but wrong for
ifeval, which has no single-line form and always carries its expression in the
brackets. So the directive was listed and never recognised.
Latent: the manifest contains no ifeval, and its only conditionals are two
single-line ifdef attribute assignments, which are still correctly treated as
not opening a block. Fixed because a pattern that names three directives and
handles two is a bug in what the code claims, not a missing feature.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review asked for AsciiDoc comment-state tracking so that a URL inside a `//`
line or a `////` block is skipped. Declining, and recording it where the next
reader of this loop will look rather than in a review thread nobody reads.
The guide has 120 files, zero commented-out URLs and zero `////` blocks, so the
tracking would govern nothing that exists. It would also weaken the gate: with
comments skipped, commenting a line out makes its finding disappear and lets the
ratchet shrink, banking a "fix" while the dead link stays in the source waiting
to be uncommented. Deleting the link is the fix.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review asked for fragment validation on same-site URLs. Measuring first: the
guide holds 42 same-site URLs with a fragment, and 38 are /javadoc/ -- the one
tree this script cannot enumerate, because it is generated from the framework
sources at build time. The other four all pointed at
`developer-guide.html#<anchor>`: this book, linking into its own body.
Those are the same defect as the fifteen `/manual/*.html` deep links already
converted in this stack, missed because they use a different path. They send a
reader out to the web to fetch the page they are reading, they do not work in
the PDF, and a renamed section breaks them silently -- the anchor lives in the
rendered book, so no amount of site-path checking can see it.
So the fix is not to validate the fragment, it is to stop writing the link:
all four become `<<anchor,text>>` xrefs, which check-guide-xrefs.py already
resolves against the rendered anchors. All three targets exist today, so this
repairs no live 404 -- it moves four unguarded links under a gate.
The rule closes the class rather than the four instances: any codenameone.com
link whose path is one of this book's own routes and which carries a fragment is
now reported. Verified non-vacuous by restoring one of the four and watching it
fail, then reverting.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both findings describe constructs the guide does not contain, so neither is a
live defect. Both are real holes in the checkers though, and each closes for a
few lines, so they close rather than getting argued with.
Conditional includes (check-guide-structure.py). A manifest entry inside an
ifdef/ifeval was skipped by the duplicate count -- correctly, since the same
chapter under two exclusive branches is one chapter in the output -- and also by
the rendered-title check, since only one branch renders. Skipping both means a
chapter included twice inside a single ACTIVE branch would pass silently. Rather
than teach the checker to distinguish exclusive branches for a construct that
does not exist (measured: zero include:: lines sit inside a conditional anywhere
in the guide), a conditional manifest entry is now refused outright, with a
message saying what to extend if one is ever wanted.
Trailing slashes on file paths (check-guide-links.py). Site paths were compared
with the trailing slash stripped, so a link to `/x.jar/` was validated against
`/x.jar`. The reviewer's premise checks out: _redirects declares the two forms as
separate routes and spells both out where both work, 32 such pairs. Preserving
the slash everywhere would break the many legitimate directory links, so the
narrow case is reported instead -- a last path segment containing a dot, wearing
a trailing slash. Directory routes such as /blog/ and /javadoc/com/codename1/io/
have no dot in the last segment and are untouched.
Both rules verified non-vacuous by introducing an instance and reading the real
exit status rather than a pipeline's: structure and links each exit 1 on the
probe and 0 once it is reverted.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review is right that the rendered-title check has slack: it counts normalized
titles across the whole book, so a nested fragment losing its heading can be
covered by an identical heading elsewhere. Confirmed rather than accepted --
removing the leveloffset from both Maven includes takes "Getting started" from 4
rendered headings to 3, while only one file declares it, so `3 >= 1` and the gate
passed a genuinely swallowed section.
Rather than give nested fragments a rendered identity, which the HTML does not
carry, this checks the cause. The spacing rule already existed but ran only on
the root manifest, and its condition was "a blank line follows". Both were wrong.
Reproduced minimally to find the real rule -- a heading is absorbed only when ALL
of these hold:
* the include is followed immediately by content, and
* neither this include nor the following one carries leveloffset (asciidoctor
brackets the content with :leveloffset: attribute entries, and an attribute
entry closes the paragraph -- the one on the FOLLOWING include lands between
the paragraph and the heading, so it protects just as well), and
* the included file does not end on a blank line, and
* its last line is ordinary paragraph text -- a delimiter, table row, heading,
attribute entry or comment all close the paragraph. That is why
_generated-build-hints.adoc, which ends on "|===", does not eat the
"Versioned builds" heading directly after it.
Against the real book, all four combinations now agree with what asciidoctor
actually renders:
both leveloffset 4 headings renders ok gate passes
only current 4 headings renders ok gate passes
only following 4 headings renders ok gate passes
neither 3 headings SWALLOWED gate fails
An earlier draft exempted only the current include and failed the third row --
a false positive on markup that renders correctly. Every guide include is now
checked, not just the manifest's: measured across all 120 files, this leaves zero
findings, because the five adjacent includes in Maven-Project-Workflow.asciidoc
are protected by leveloffset.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… the rest
Three link findings, and only the first was a live defect -- in the rule I added
one commit earlier.
The trailing-slash rule was unconditional, so it reported "/videos.html/" as
broken even though _redirects declares that exact source and the site serves it.
The rules here all compile slash-insensitively ("^...$/?"), which is right for
matching but discards the distinction the site actually draws, so the raw sources
are now collected alongside them and a slashed file path is reported only when
_redirects does not spell it out. Probed both ways: "/videos.html/" passes,
"/files/CodenameOneBuildClient.jar/" fails.
Hugo route overrides. hugo.toml sets neither [permalinks] nor uglyURLs today, so
deriving a route as "section path + slug" is currently correct; if either
appears, every route underneath moves and this would go on accepting links to
paths Hugo no longer publishes. Modelling a configuration that is not there would
be guesswork, and reading the built public/ tree makes a local run depend on a
tree that may be stale or absent. So it now notices instead: either key present
aborts the check with what to do about it. Probed with each key in turn.
Protocol-relative links and fragments on ordinary same-site pages are declined,
with the measurements recorded in the code rather than in a review thread nobody
reads. The guide has no `link://` macro at all, and its one bare "//host/path" is
a JavaScript string inside a source block, so widening URL_RE to match "//" would
start reporting code as a broken link. And of the 42 same-site URLs carrying a
fragment, 38 are /javadoc/ -- generated at build time, exempt for that reason --
while the other four pointed into this book and are now xrefs, which
check-guide-xrefs.py resolves against the rendered anchors. Nothing is left that
this script could check without building the Hugo site.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ting
Two more holes in the guards. Neither has an instance in the guide today, and
both close cheaply enough that arguing about them would cost more.
check-guide-xrefs.py rendered the book once, with the default backend, so
anything inside ifdef::backend-pdf[] was dropped before a single reference in it
could be examined -- while the workflow publishes an asciidoctor-pdf build from
the same source. Native-Themes.asciidoc already has two such branches. It now
renders twice, the second time with -a backend-pdf, which selects exactly the
content the PDF build includes without needing asciidoctor-pdf here. The two runs
are checked SEPARATELY rather than pooled: an anchor that exists only in the HTML
branch must not satisfy a reference made in the PDF branch. Findings name the
branch they came from.
A/B with a dangling xref planted inside the backend-pdf branch of
Native-Themes.asciidoc:
old "Cross-references OK: 1741 anchors" -- exit 0
new "Native-Themes.asciidoc:506: <<definitely-not-a-real-anchor>> ...
(1 reference(s), pdf render)" -- exit 1
check-missing-code-blocks.py accepted any `[attribute]` line as the start of the
promised block, so an admonition standing where a listing used to be hid the
hole. An admonition is prose and can never be the listing a sentence promised, so
the five names are excluded. Excluded by name rather than by whitelisting the
kinds that ARE code: measured, the bracket lines legitimately answering a
promising sentence already span [source] (582), [cols=...] and [options=...] (56),
[listing] (3), [quote] (2) and an anchored image, and a whitelist would report the
next kind nobody anticipated.
A/B with each block planted after a promising sentence, using the real signature
of a removed listing -- the two blank lines it leaves behind:
[NOTE] old: none new new: reported as a hole
[source,java] old: none new new: none new
The baseline is unchanged at 402, because the guide currently has no admonition
sitting in a listing's place.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…separator
Two findings, and this time the first one is live.
The rendered book contains exactly three relative hrefs, and all three are
broken: css.asciidoc carried link:Images[], link:Fonts[] and
link:Supported-Properties#text-decoration[] over from the wiki this guide
replaced, naming pages that were never carried across with them. The guide
renders as a single page, so a relative href resolves against wherever that page
is served and reaches nothing that ships with it -- the reader gets a 404 while
every gate reported success. All three targets are sections in that same file:
[[Images]] already existed, [[Fonts]] is added to match it, and
[[text-decoration]] was already there and already used by a sibling reference.
check-guide-xrefs.py now reports any relative href, which after this change has
zero instances. Absolute URLs stay with check-guide-links.py, and root-relative
paths and fragments are excluded. A/B with one link restored: old printed
"Cross-references OK" and exited 0, new reports it and exits 1.
The second is a wrong claim in my own comment. check-guide-structure.py returned
early when an include was followed by ifdef/ifndef/ifeval/endif, on the reasoning
that "a preprocessor directive is not content and cannot absorb a paragraph".
True, and irrelevant: asciidoctor REMOVES the directive during preprocessing, so
it does not separate anything either. Measured -- with `ifdef::backend-html5[]`
between an include ending in prose and a following heading, the render contains
zero heading tags and the paragraph reads "...preferred tools. === A parent-local
heading". The rule now scans past directive lines to the first line that survives
preprocessing. A/B on that construct: old passed it, new reports it.
Note the rendered-title check cannot cover this case either, because a
parent-local heading is not the first heading of any included file.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:f9e50cb8cc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-links.py
Comment threadscripts/developer-guide/check-guide-xrefs.py
… has a limit
The first finding was live: the link ratchet held a working link.
docs/website/functions/[[path]].js runs after context.next() has already 404ed
and then redirects anything under /files/ or /demos/ to
download.codenameone.com. Those paths are served, so the model was missing a
whole class of route and had banked a real one -- /files/iOS_UI-Kit.psd -- as
broken. The destination is off-site, which puts it in the same bucket as every
other off-site redirect: reachable, not verifiable from this repository. The
rules are appended AFTER the _redirects rules because the function is a fallback
and the first matching rule wins, mirroring how the host evaluates. The baseline
shrinks 37 -> 36, and a new /files/ link is now accepted rather than failing CI.
The second is a real limit of the PDF surrogate, and one the command line cannot
remove. Setting backend-pdf makes ifndef::backend-pdf[] content disappear and
ifdef::backend-pdf[] content appear -- verified, and that is the only form this
guide uses. It does not undefine backend-html5: the HTML converter sets that
itself, after command-line attributes are applied, so even `-a backend-html5!`
leaves it defined. Measured both ways. Content guarded on the HTML backend would
therefore survive into the surrogate render and could satisfy a PDF-only
reference that the real asciidoctor-pdf build leaves dangling.
The guide has no such conditional, so rather than model a construct that is not
there -- or drive asciidoctor-pdf and try to read anchors back out of a PDF --
the checker refuses one if it appears, naming the alternative the rest of the
guide already uses. Probed: planting an ifdef::backend-html5[] block aborts the
check with that message.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:6a8963d02a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-structure.py Outdated
…side one
Both scanners matched a delimiter of exactly four characters, so a block opened
with ----- was invisible to them and its contents were scanned as live markup.
The guide has 20 such lines -- eight in Desktop-Integration, six in
Working-With-iOS, six in appendix_goal_generate_archetype -- every one an
ordinary [source] block wrapping an include::. validate-guide-snippets.py already
accepted -{4,}; these two had drifted from it.
Nothing is misread today, because those includes name .java, .xml and .properties
files and the reachability walk skips non-asciidoc targets. The failure mode is
still the interesting one though: it is a FALSE POSITIVE generator, not a missed
defect. A/B with each construct planted inside a five-dash block:
a displayed include::Working-with-UWP.asciidoc[]
old reported a swallowed-heading error against markup that only appears
on the page as text
new clean
a displayed "The wrapper looks like this:" followed by blank lines
old reported it as a missing code block
new clean
Both now track the delimiter that opened the block and close on one of the same
character AND length, which is what asciidoctor does -- so a four-dash line
inside a five-dash block is content rather than the close. The set stays limited
to literal blocks: an example (====) or sidebar (****) block contains live
markup, so headings and includes inside one are real and must keep counting.
Baselines unchanged: 119 included documents, 402 holes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:57212b7f43

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-structure.py
Comment threadscripts/developer-guide/check-guide-xrefs.py
shai-almogand others added 2 commits September 2, 2026 18:21
* Guide figures render with a bundled font instead of the host's
None of the 24 generated figures could be reproduced outside CI. Running the
generator on a Mac reproduces 0 of 24 byte-for-byte, with 3-20% of pixels
differing. Two consecutive local runs are byte-identical, so the generator is
deterministic on one host; the variable is the font.
`PreAdvancedThemingScreenshots` styled with `Font.createSystemFont`, which
resolves through `JavaSEPort.fontFaceSystem` -- "Arial" on macOS and Linux
alike. Arial exists on a developer's Mac and not on a stock CI runner, so AWT
silently substitutes and every glyph changes. The differences are exactly that:
for `flow-layout.png` every differing pixel sits in y 10-130, the title and
label rows, while the colored blocks below match to the pixel.
So the byte-exact gate has only ever been green because CI both generates and
verifies. A developer regenerating locally could not match it, and at the scale
the guide's remaining ~260 app screenshots would need, that is untenable.
The figures now load `native:MainRegular`, which `JavaSEPort.loadTrueTypeFont`
reads from `/com/codename1/impl/javase/Roboto-Medium.ttf` on the classpath
rather than from an installed-font lookup. This is also what the project's font
rule requires everywhere: never `createSystemFont`, always the `native:` scheme.
A null return refuses loudly rather than falling back to a host font, because a
silent fallback would restore the exact non-determinism this removes.
The committed baselines are regenerated from a Mac. CI byte-compares them on
Linux, so the check either passes -- proving host independence rather than
asserting it -- or fails and says so immediately.
Composition, dimensions and colours are unchanged; only the typeface moves.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Pin the font on every component, not just the ones that get block colours
CI reported one stale figure out of 24: `guibuilder-2-insets-3.png`, the only
one containing a `TextField`. The other 23 matched Linux byte for byte, so the
bundled-font change worked -- it just did not reach far enough.
`applyBlockStyleToContent` styled `Label` and `Button`. Everything else kept the
theme's default font, which resolves through the host, so the text field and its
hint were still host-dependent. Enumerating the types that carry text would have
left the next one added broken the same way, so the walk now pins the face on
every component it visits, plus the hint label, which is painted by a `Label`
that is not in the component tree and so is never reached by the walk.
The first attempt used `BLOCK_FONT` for this and regressed the figure: at 29px
against the theme default's 13px the field grew and squeezed "Submit" down to a
clipped sliver. `FIELD_FONT` is sized to reproduce the original height, so the
composition is unchanged and only the typeface moves.
Verified locally by rendering twice, once with `JavaSEPort.setFontFaces` pointed
at a family that does not exist -- which is what a machine without Arial looks
like to the port. All 24 come out byte-identical, so nothing in these figures
reads an installed font any more.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Publish what the runner actually rendered when a screenshot fails
"Committed screenshot is stale: <name>" names the file and nothing else, so
there is no way to tell a real regression from an environment difference
without adding a debugging round trip to CI. The generated directory is now
uploaded as an artifact when the step fails.
Needed immediately: one figure still differs between a Mac and the runner after
the font fix, and host fonts, JDK version and working directory have each been
ruled out locally.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Pin MigLayout's platform, and stop demanding byte equality of a glyph edge
The artifact step paid for itself immediately. Downloading what the runner
rendered showed two figures differing, not the one the gate reported -- it exits
on the first mismatch -- and the two had nothing in common.
`mig-layout.png` differed by 12.8% of its pixels, a real layout shift.
MigLayout takes its default gaps from `PlatformDefaults`, which reads
`System.getProperty("os.name")` and answers MAC_OSX, GNOME or WINDOWS_XP, each
with different spacing. The figure was rendering with macOS gaps on a Mac and
GNOME gaps on the runner. Pinning the platform fixes it: with the pin, 23 of the
24 figures now match the runner's own output byte for byte.
`layered-layout.png` differed by 173 pixels, 0.113%, inside a 25x25 box. That
one is not fixable. Measured against the runner's bytes, the material glyph
lands at exactly the same size and the same origin -- a 55x49 bounding box --
and differs only in antialiased edge coverage, 946 fully-white pixels against
916. Java2D rasterizes the same glyph, from the same bundled font, at the same
size, slightly differently on the two platforms. Demanding byte equality there
would mean deleting legitimate content from the figure or carrying a
permanently red check.
So the comparison moves from `cmp -s` to a comparer that still requires byte
equality by default and accepts a bounded difference only where a figure carries
a `.tolerance` sidecar explaining itself, in the same key=value shape the CN1SS
suites already use. The area bound does the work: a per-pixel delta that large is
meaningless on its own, since a glyph edge flips between white and the block
behind it, but a regression that changed the icon would move far more than 0.3%
of the image.
Verified by running the comparer with the runner's own output against the
committed figures -- which is exactly what CI will do -- and by four probes: an
untoleranced figure that differs fails, the toleranced figure fails when changed
beyond its budget, a missing figure fails the count, and a clean run passes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Guard the iOS font branch, and stop the comparator gating itself out
Two review findings on this PR.
The first says `native:MainRegular` does not give host-independent output,
because `loadTrueTypeFont` resolves native fonts to the first installed SF or
Helvetica family before reaching the bundled Roboto. The branch is real, but it
is reached only when `isIOS` is set, which `loadSkinFile` does for a skin whose
systemFontFamily contains "helvetica" -- and this generator never loads a skin.
The measurement agrees: figures rendered on a Mac match the Linux runner byte
for byte, which could not happen if one side were resolving Helvetica Neue and
the other Roboto.
So the conclusion does not hold today, but the risk is real for tomorrow: a
change that loads a skin here would put host fonts back into the output with no
other symptom. The generator now refuses to run under an iOS platform, and says
why, rather than leaving that to a comment nobody reads.
The second finding is straightforwardly right and is the more serious of the
two. `on.pull_request.paths` triggers on `scripts/developer-guide/**`, but the
`Determine changed components` filter named only two scripts, so a pull request
touching any other script here started the workflow with `docs` false and
skipped the steps that script governs. A change to compare-screenshots.py could
have merged without the screenshot check ever running it -- a gate that skips
itself. The filter now covers the whole directory, which also removes the
two-copies-drift the surrounding comment already warns about.
Verified the guard changes no output: all 24 figures are unchanged, and the
comparer still passes against the runner's own bytes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Count every changed pixel toward the area budget, not just the loud ones
The comparator incremented its mismatch counter only for pixels whose channel
delta EXCEEDED maxChannelDelta, which is what the CN1SS comparator does. With a
sidecar written the way this one was -- a large delta bound paired with a small
area bound -- that leaves an unbounded hole, and review gave the exploit in the
figures' own palette: recolouring the green #06a806 to #a608a6 moves every
channel by exactly 160, so with maxChannelDelta=160 not one pixel is counted and
a dramatically different image reports zero mismatches.
Reproduced it before fixing: the recolour changes 4191 pixels, 2.73% of the
image, at a worst delta of exactly 160 -- and passed.
The two bounds are now independent. maxMismatchPercent limits how much of the
image may change at all, counting every differing pixel; maxChannelDelta caps how
far any single pixel may move. The measured legitimate noise -- 173 pixels,
0.113%, worst delta 141 -- still passes, the recolour now fails on area, and a
five-pixel solid overwrite fails on delta.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Correct the sidecar's rationale to match how the bounds now work
The previous commit changed the comparator so every differing pixel counts
toward the area budget and the channel delta is a separate ceiling. The sidecar
still explained the old behaviour -- that the area bound did the work and the
delta was meaningless on its own -- which is now wrong in a file whose whole
purpose is to justify the numbers beside it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Compare figures in RGBA, because they have an alpha channel
The comparator converted both images to RGB before counting differences. The
committed figures are genuine RGBA PNGs -- four channels, currently opaque
everywhere -- so that dropped a real channel, and any change confined to alpha
was invisible to the tolerance path. A regression that turned the whole figure
transparent while leaving every colour channel intact reported zero changed
pixels and passed.
Byte equality, which every figure without a sidecar is still held to, always
caught this. Only a figure carrying a sidecar could reach the weakened path, so
today the exposure was one image -- but that image is exactly the one whose
comparison is relaxed.
Verified: making layered-layout.png fully transparent with its RGB channels
untouched now reports 100% of pixels changed, the real runner output still
passes, and the recolour and untoleranced-difference probes still fail as they
did.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…page
Two findings, one of them a false positive I introduced last round.
Comment blocks. Asciidoctor drops a //// block entirely, so an include inside
one does not put its target in the book and prose inside one promises the reader
nothing -- but both scanners read straight through. //// joins the fence set in
each. A/B with each construct planted inside a comment block: a displayed
include:: made the structure check fail, and a promising sentence made the
missing-block check fail; both are clean now.
Note this is deliberately NOT symmetric with check-guide-links.py, which still
reads URLs inside comments. The reasons differ and the code says so: a dead link
commented out is still dead debt in the source, and letting the ratchet shrink
for it would make "comment the line out" a way to silence that gate. Reachability
is the opposite -- calling a commented-out include reachable states something
about the book that is simply untrue.
Packaged assets. The relative-href rule added last round was too broad: the HTML
packaging step copies every subdirectory of docs/developer-guide next to
developer-guide.html, so link:img/example.png[] resolves for a reader who opens
the zip, and the gate would have rejected it. A relative href that names an
existing file under the guide directory is now allowed. sketch/ is not, because
that is the one directory the packaging loop skips.
Probed all four ways, since an exemption is only worth having if it still
rejects:
link:img/flow-layout.png[] exit 0 (real, packaged)
link:img/does-not-exist.png[] exit 1
link:sketch/whatever.svg[] exit 1 (not packaged)
link:Supported-Properties[] exit 1 (the wiki-era shape)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:a6bd5197dc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-xrefs.py Outdated
Comment threadscripts/developer-guide/check-guide-xrefs.py
shai-almogand others added 2 commits September 2, 2026 18:35
Both findings are defects in the two rules added in the previous commit.
packaged_asset() asked whether the file exists here, which is not the question. A
link only works if the published output carries it, and the two outputs filter
differently:
* the HTML archive copies only the SUBDIRECTORIES of docs/developer-guide next
to developer-guide.html and skips sketch, so a root-level file is never in the
zip however much it exists in the tree;
* the website rsync excludes sketch/, *.asciidoc and *.adoc, so a source file
nested inside a packaged directory still does not reach the site.
So link:Introduction.asciidoc[] was being exempted and readers would follow it to
nothing. Both filters are now reproduced. The permissive direction is the
expensive one here -- it suppresses a finding for a link nobody can follow --
so all four cases are probed:
img/flow-layout.png exit 0 in both outputs
Introduction.asciidoc exit 1 in neither
img/does-not-exist.png exit 1
sketch/foo.svg exit 1 excluded by both
The backend-conditional rejection scanned raw lines, so a source block DISPLAYING
`ifdef::backend-html5[]` as example text aborted the whole gate. A displayed
directive takes no part in preprocessing. It now tracks literal and comment block
delimiters the way the other guide scanners do. A/B: displayed inside a source
block went 1 -> 0, while a genuinely active conditional still exits 1.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…18 statistics (#5671)
* The Introduction taught a dead IDE menu, an iTunes walkthrough and 2018 statistics
Six corrections to the chapter a new developer reads first:
* The JavaScript target was described twice and contradicted itself: the
overview said TeaVM did the translation, the port section said ParparVM with
TeaVM as a fallback. The port section is right -- `javascript.port` is
declared with `parparvm` as its default and `teavm` documented as "the
original builder as a compatibility fallback".
* `http://teavm.org:[TeaVM-based builder]` -- the stray colon made the macro a
bare host, and the site serves TLS. Removed from the link ratchet, which now
stands at 37.
* Bitcode was cited as something ParparVM absorbed without modification. Apple
has since withdrawn bitcode, which makes the point better than the arrival
did, so the sentence now names both moves.
* The device-fragmentation section rested on two 2018 Android share numbers,
followed by a sentence conceding they would be stale on arrival. The
structural claim survives without them.
* The iOS developer fee was dated with "for 10 years at the time of this
writing".
* The UDID instructions walked the reader through iTunes, with a screenshot of
iTunes on iOS 9.3.5 that also exposed a real device serial and UDID. iTunes
has not existed on macOS since Catalina, and the signing chapter already gives
the current answer, so this now points there.
* The device build was "a right click away" via the IDE plugin, illustrated by a
menu still offering Blackberry, J2ME and Windows Phone builds. Replaced with
the `cn1:buildAndroid` and `cn1:buildIos` goals, taken from the mojo names.
Both screenshots are deleted, so find_unused_images.py stays satisfied.
Verified: asciidoctor at --failure-level WARN, Vale at suggestion level,
LanguageTool (status ok, 0 matches, run under JDK 17 rather than the JDK 8
false green), paragraph capitalization, snippets, xrefs, structure, links and
missing-code-blocks all clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* "-pl common" made a device build a no-op that reported success
Review caught the command I had just written. AbstractBuildWrapperMojo.execute()
opens with:
if (!project.isExecutionRoot()) {
getLog().info("Skipping execution for non-root project");
return;
}
and BuildAndroidMojo, BuildIosMojo and BuildWin32Mojo all inherit it unchanged.
Under `-pl common` the selected module is not the execution root, so the goal
logs that line and stops. The design is deliberate -- the wrapper re-invokes
Maven on the root pom itself (`request.setPomFile(new File("pom.xml"))`) -- so
naming a module both skips the wrapper and defeats its purpose.
Measured rather than reasoned, against scripts/hellocodenameone:
mvn -pl common ...:buildWin32 -> "Skipping execution for non-root project"
BUILD SUCCESS, nothing built
mvn ...:buildWin32 -> no skip line, proceeds into the reactor
A silent success is the worst failure mode for a getting-started instruction, so
the Introduction now says where to run it and warns about the module form
explicitly.
Working-With-Windows carried the same `-pl common` shape and is fixed with it --
it is where I copied the form from, so leaving it would reintroduce the defect
the next time someone follows the pattern.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* The Windows build wrappers named a platform no profile answers to
Review checked the command this PR documents and found it still builds nothing.
The wrapper passes `codename1.platform` to its nested Maven run, and that
property is what activates the module profile in a generated project's root pom.
The profile is `win`, matching the value the win module itself declares:
<profile><id>win</id>
<activation><property>
<name>codename1.platform</name><value>win</value>
</property></activation>
<modules><module>win</module></modules>
BuildWin32Mojo and BuildWindowsDeviceMojo both passed "windows", which matches no
profile at all. Nothing else in the plugin reads the platform as "windows", so
the value was simply inert: the win module never joined the reactor and the
nested build reported success having produced no Windows binary. Every other
wrapper already agrees with its profile -- android, ios, javascript, linux and
javase all match -- so these two were the only ones out of step.
Verified by A/B against scripts/hellocodenameone, reading the NESTED reactor
rather than the outer one:
before hellocodenameone, -common, -javase <- no win module
after hellocodenameone, -common, -win
The build TARGET is a separate namespace and stays "windows-device"
(Executor.BUILD_TARGET_WINDOWS_NATIVE), which is correct.
SpotBugs over codenameone-maven-plugin regenerated: 0 findings.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* The primary Win32 command had both defects the convenience goal just lost
Fixing BuildWin32Mojo made the sentence beside this snippet false: the goal now
submits a build, while the command the chapter leads with still could not. It
carried both defects at once --
mvn -pl common package -Dcodename1.platform=windows ...
-- `-pl common` builds that module instead of the reactor, so the win module is
never reached, and `windows` matches no profile even when the reactor is whole.
The two mistakes hid each other: with only the module selected, the platform
value had nothing left to activate.
Corrected to what the wrapper actually runs, read off AbstractBuildWrapperMojo:
goal `package`, `codename1.platform=win`, `codename1.buildTarget=windows-device`,
from the project root.
A/B against scripts/hellocodenameone, reading the reactor, which Maven prints
before it compiles:
old no win module in the reactor at all
new hellocodenameone, -common, -win
Both invocations then fail identically on this machine with "invalid target
release: 17", because tools/env.sh pins JDK 8 while the demo targets 17. That is
environmental and equal on both sides, so it does not affect the comparison.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@shai-almog
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Gates for the developer guide defects Asciidoctor cannot see - #5669

Open
shai-almog wants to merge 17 commits into
masterfrom
devguide-guards
Open

Gates for the developer guide defects Asciidoctor cannot see#5669
shai-almog wants to merge 17 commits into
masterfrom
devguide-guards

Conversation

@shai-almog

Copy link
Copy Markdown
Collaborator

Split out of #5664 so the structural repairs there stop waiting on tooling review. Stacked on it, because these checks are written against a guide that #5664 fixes — run them on master and several fail by design.

Four checks. Each was verified to fail when the defect it describes is reintroduced, and to pass again when it is reverted.

CheckCatchesAsciidoctor's behaviour
check-guide-structure.pya document in the tree but not in the book; a chapter that silently becomes a part, a subsection, or a duplicate; an included chapter whose title does not survive into the rendered outputsilent
check-guide-xrefs.pyan internal link that resolves to nothing, and one that renders as a bare [some-id]silent — a reference with link text renders as an ordinary link to nowhere
check-missing-code-blocks.pyprose that promises a listing where a hole followsnot its job
check-guide-links.pylinks checked against the paths the site actually servesnot its job

How the link checker decides

It does not carry a list of good links. It derives what the site serves and follows the rules to what they produce:

  • _redirects sources compiled to patterns, splat and :placeholder included, then followed to their destination rather than accepted for matching. /*.html matches every root-level .html path there is, so a match proves nothing.
  • Hugo routes from front matter — url, slug, aliases — because 1055 of the 1262 content pages override their route with slug, and deriving it from the filename both invents routes and rejects real ones.
  • Draft and future-dated pages excluded, since Hugo defaults buildDrafts and buildFuture to false.
  • Static assets enumerated as files; a directory without an index.html is not a route.
  • /developer-guide/ derived from what scripts/website/build.sh rsyncs. Only /javadoc/ stays exempt, because it is produced from the framework sources and cannot be enumerated here.

Ratchets

check-missing-code-blocks.py records 406 existing holes and check-guide-links.py38 bad links. Both are multisets — one line per occurrence, because a file naming the same bad URL twice has two problems.

Both may only shrink. A fixed entry that is still baselined fails, so the ratchet actually banks its fixes; growing one requires --allow-new, so recording new debt is deliberate rather than a side effect of regenerating.

Where they run

developer-guide-docs.yml runs all four. check-guide-links.py also runs from website-docs.yml, which triggers on docs/website/** — a page renamed or re-slugged there breaks the guide, and putting those paths into the guide workflow would run the whole guide build, maven install and screenshot generation included, for every blog post the daily publisher pushes.

The docs paths filter now covers scripts/developer-guide/** rather than two named files, because a change to a script not in that filter started the workflow while skipping the steps that script governs.

@chatgpt-codex-connector

chatgpt-codex-connectorBot commented Sep 2, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

ReviewStatusCommitReview trigger
📝 Code Review🔄 Running since 2026-09-02T15:38:41.729150Za9c0b73New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:2da41dd8c7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-links.py Outdated
Comment threadscripts/developer-guide/check-guide-structure.py

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:28f950b311

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-links.py

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:21e513112f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-links.py Outdated
@github-actions

Copy link
Copy Markdown
Contributor

Cloudflare Preview

@github-actions

github-actionsBot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Developer Guide build artifacts are available for download from this workflow run:

Developer Guide quality checks:

  • AsciiDoc linter: No issues found (report)
  • Vale: No alerts found (report)
  • Paragraph capitalization: No paragraph capitalization issues (report)
  • LanguageTool: No grammar matches (report)
  • Image references: No unused images detected (report)

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:6d0560cc05

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-structure.py
Comment threadscripts/developer-guide/check-guide-links.py

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:b96d1321a1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-links.py Outdated
Comment threadscripts/developer-guide/check-guide-links.py
@shai-almog
shai-almogforce-pushed the devguide-structure-and-guards branch from 6108cad to e113f04CompareSeptember 2, 2026 11:57

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:d82c0ea525

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-structure.py

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:42189c33d9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-links.py
Comment threadscripts/developer-guide/check-guide-links.py

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:89e588ddc8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-missing-code-blocks.py Outdated
Comment threadscripts/developer-guide/check-guide-xrefs.py Outdated

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:b88150d821

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-xrefs.py
Comment threadscripts/developer-guide/check-guide-structure.py Outdated
Base automatically changed from devguide-structure-and-guards to masterSeptember 2, 2026 13:19
shai-almogand others added 10 commits September 2, 2026 16:20
Four checks, split out of the structural repairs they were written alongside so
each can be reviewed on its own terms. Every one was verified to fail when the
defect it describes is reintroduced, and to pass again when it is reverted.
check-guide-structure.py -- every document is in the book or declared out of it;
no chapter silently becomes a part, a subsection, or a duplicate; and, by
rendering the book and reading its headings back, every included chapter's title
survives into the output. That last check is what would have caught a whole
chapter rendering as subsections of the one before it.
check-guide-xrefs.py -- every internal link resolves, and none renders as a bare
"[some-id]". Asciidoctor reports neither: a reference carrying link text renders
as an ordinary link to nowhere.
check-missing-code-blocks.py -- prose that promises a listing where a hole
follows. 406 exist, from the snippet extraction in bbdc605, recoverable from
bbdc605~1.
check-guide-links.py -- links checked against the paths the site actually
serves, derived from _redirects and the Hugo content tree and followed through
every redirect rule to what it produces, rather than accepted for matching one.
The two ratchets may only shrink; growing one needs --allow-new, so recording
debt is deliberate rather than a side effect of regenerating.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The stacked content change restored the video capture constraints examples, so
their baseline entries went stale and the ratchet refused to pass until they
were removed. 406 to 402.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The outcome check compared counts of normalized headings at any depth, which a
collision satisfies: "Analytics" is a chapter and also a subsection of Commerce,
so the rendered book contains it twice. If the chapter were swallowed, the
subsection kept the count at one and the check -- the central safeguard of this
change -- passed. Measured on the current book: analytics appears twice at any
level and once at chapter level; "getting started" four times at any level and
never at chapter level.
Manifest entries are now counted against chapter-level headings only. Nested
fragments still use the any-level count, because their depth is whatever their
parent gives them.
Second finding: resolves() tried every matching redirect rule and passed if any
chain worked. The host applies the FIRST match and stops, so a link matching an
early rule that leads somewhere deleted would have passed on the strength of a
later rule the reader never reaches. It now returns the first match's result. No
guide link changes verdict today; the point is that the model matches the
behaviour it claims to model.
Also declined, with the reasoning left in the code rather than a review thread:
adding Markdown's three-backtick fence to the literal-block list. The guide
contains none, and validate-guide-snippets.py requires every listing to be
[source,LANG] with a bare include:: inside ---- delimiters, so such a block
would fail that gate before reaching this one.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The pattern required empty brackets, which is right for ifdef and ifndef -- with
content they are the single-line form and guard only that line -- but wrong for
ifeval, which has no single-line form and always carries its expression in the
brackets. So the directive was listed and never recognised.
Latent: the manifest contains no ifeval, and its only conditionals are two
single-line ifdef attribute assignments, which are still correctly treated as
not opening a block. Fixed because a pattern that names three directives and
handles two is a bug in what the code claims, not a missing feature.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review asked for AsciiDoc comment-state tracking so that a URL inside a `//`
line or a `////` block is skipped. Declining, and recording it where the next
reader of this loop will look rather than in a review thread nobody reads.
The guide has 120 files, zero commented-out URLs and zero `////` blocks, so the
tracking would govern nothing that exists. It would also weaken the gate: with
comments skipped, commenting a line out makes its finding disappear and lets the
ratchet shrink, banking a "fix" while the dead link stays in the source waiting
to be uncommented. Deleting the link is the fix.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review asked for fragment validation on same-site URLs. Measuring first: the
guide holds 42 same-site URLs with a fragment, and 38 are /javadoc/ -- the one
tree this script cannot enumerate, because it is generated from the framework
sources at build time. The other four all pointed at
`developer-guide.html#<anchor>`: this book, linking into its own body.
Those are the same defect as the fifteen `/manual/*.html` deep links already
converted in this stack, missed because they use a different path. They send a
reader out to the web to fetch the page they are reading, they do not work in
the PDF, and a renamed section breaks them silently -- the anchor lives in the
rendered book, so no amount of site-path checking can see it.
So the fix is not to validate the fragment, it is to stop writing the link:
all four become `<<anchor,text>>` xrefs, which check-guide-xrefs.py already
resolves against the rendered anchors. All three targets exist today, so this
repairs no live 404 -- it moves four unguarded links under a gate.
The rule closes the class rather than the four instances: any codenameone.com
link whose path is one of this book's own routes and which carries a fragment is
now reported. Verified non-vacuous by restoring one of the four and watching it
fail, then reverting.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both findings describe constructs the guide does not contain, so neither is a
live defect. Both are real holes in the checkers though, and each closes for a
few lines, so they close rather than getting argued with.
Conditional includes (check-guide-structure.py). A manifest entry inside an
ifdef/ifeval was skipped by the duplicate count -- correctly, since the same
chapter under two exclusive branches is one chapter in the output -- and also by
the rendered-title check, since only one branch renders. Skipping both means a
chapter included twice inside a single ACTIVE branch would pass silently. Rather
than teach the checker to distinguish exclusive branches for a construct that
does not exist (measured: zero include:: lines sit inside a conditional anywhere
in the guide), a conditional manifest entry is now refused outright, with a
message saying what to extend if one is ever wanted.
Trailing slashes on file paths (check-guide-links.py). Site paths were compared
with the trailing slash stripped, so a link to `/x.jar/` was validated against
`/x.jar`. The reviewer's premise checks out: _redirects declares the two forms as
separate routes and spells both out where both work, 32 such pairs. Preserving
the slash everywhere would break the many legitimate directory links, so the
narrow case is reported instead -- a last path segment containing a dot, wearing
a trailing slash. Directory routes such as /blog/ and /javadoc/com/codename1/io/
have no dot in the last segment and are untouched.
Both rules verified non-vacuous by introducing an instance and reading the real
exit status rather than a pipeline's: structure and links each exit 1 on the
probe and 0 once it is reverted.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review is right that the rendered-title check has slack: it counts normalized
titles across the whole book, so a nested fragment losing its heading can be
covered by an identical heading elsewhere. Confirmed rather than accepted --
removing the leveloffset from both Maven includes takes "Getting started" from 4
rendered headings to 3, while only one file declares it, so `3 >= 1` and the gate
passed a genuinely swallowed section.
Rather than give nested fragments a rendered identity, which the HTML does not
carry, this checks the cause. The spacing rule already existed but ran only on
the root manifest, and its condition was "a blank line follows". Both were wrong.
Reproduced minimally to find the real rule -- a heading is absorbed only when ALL
of these hold:
* the include is followed immediately by content, and
* neither this include nor the following one carries leveloffset (asciidoctor
brackets the content with :leveloffset: attribute entries, and an attribute
entry closes the paragraph -- the one on the FOLLOWING include lands between
the paragraph and the heading, so it protects just as well), and
* the included file does not end on a blank line, and
* its last line is ordinary paragraph text -- a delimiter, table row, heading,
attribute entry or comment all close the paragraph. That is why
_generated-build-hints.adoc, which ends on "|===", does not eat the
"Versioned builds" heading directly after it.
Against the real book, all four combinations now agree with what asciidoctor
actually renders:
both leveloffset 4 headings renders ok gate passes
only current 4 headings renders ok gate passes
only following 4 headings renders ok gate passes
neither 3 headings SWALLOWED gate fails
An earlier draft exempted only the current include and failed the third row --
a false positive on markup that renders correctly. Every guide include is now
checked, not just the manifest's: measured across all 120 files, this leaves zero
findings, because the five adjacent includes in Maven-Project-Workflow.asciidoc
are protected by leveloffset.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… the rest
Three link findings, and only the first was a live defect -- in the rule I added
one commit earlier.
The trailing-slash rule was unconditional, so it reported "/videos.html/" as
broken even though _redirects declares that exact source and the site serves it.
The rules here all compile slash-insensitively ("^...$/?"), which is right for
matching but discards the distinction the site actually draws, so the raw sources
are now collected alongside them and a slashed file path is reported only when
_redirects does not spell it out. Probed both ways: "/videos.html/" passes,
"/files/CodenameOneBuildClient.jar/" fails.
Hugo route overrides. hugo.toml sets neither [permalinks] nor uglyURLs today, so
deriving a route as "section path + slug" is currently correct; if either
appears, every route underneath moves and this would go on accepting links to
paths Hugo no longer publishes. Modelling a configuration that is not there would
be guesswork, and reading the built public/ tree makes a local run depend on a
tree that may be stale or absent. So it now notices instead: either key present
aborts the check with what to do about it. Probed with each key in turn.
Protocol-relative links and fragments on ordinary same-site pages are declined,
with the measurements recorded in the code rather than in a review thread nobody
reads. The guide has no `link://` macro at all, and its one bare "//host/path" is
a JavaScript string inside a source block, so widening URL_RE to match "//" would
start reporting code as a broken link. And of the 42 same-site URLs carrying a
fragment, 38 are /javadoc/ -- generated at build time, exempt for that reason --
while the other four pointed into this book and are now xrefs, which
check-guide-xrefs.py resolves against the rendered anchors. Nothing is left that
this script could check without building the Hugo site.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ting
Two more holes in the guards. Neither has an instance in the guide today, and
both close cheaply enough that arguing about them would cost more.
check-guide-xrefs.py rendered the book once, with the default backend, so
anything inside ifdef::backend-pdf[] was dropped before a single reference in it
could be examined -- while the workflow publishes an asciidoctor-pdf build from
the same source. Native-Themes.asciidoc already has two such branches. It now
renders twice, the second time with -a backend-pdf, which selects exactly the
content the PDF build includes without needing asciidoctor-pdf here. The two runs
are checked SEPARATELY rather than pooled: an anchor that exists only in the HTML
branch must not satisfy a reference made in the PDF branch. Findings name the
branch they came from.
A/B with a dangling xref planted inside the backend-pdf branch of
Native-Themes.asciidoc:
old "Cross-references OK: 1741 anchors" -- exit 0
new "Native-Themes.asciidoc:506: <<definitely-not-a-real-anchor>> ...
(1 reference(s), pdf render)" -- exit 1
check-missing-code-blocks.py accepted any `[attribute]` line as the start of the
promised block, so an admonition standing where a listing used to be hid the
hole. An admonition is prose and can never be the listing a sentence promised, so
the five names are excluded. Excluded by name rather than by whitelisting the
kinds that ARE code: measured, the bracket lines legitimately answering a
promising sentence already span [source] (582), [cols=...] and [options=...] (56),
[listing] (3), [quote] (2) and an anchored image, and a whitelist would report the
next kind nobody anticipated.
A/B with each block planted after a promising sentence, using the real signature
of a removed listing -- the two blank lines it leaves behind:
[NOTE] old: none new new: reported as a hole
[source,java] old: none new new: none new
The baseline is unchanged at 402, because the guide currently has no admonition
sitting in a listing's place.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…separator
Two findings, and this time the first one is live.
The rendered book contains exactly three relative hrefs, and all three are
broken: css.asciidoc carried link:Images[], link:Fonts[] and
link:Supported-Properties#text-decoration[] over from the wiki this guide
replaced, naming pages that were never carried across with them. The guide
renders as a single page, so a relative href resolves against wherever that page
is served and reaches nothing that ships with it -- the reader gets a 404 while
every gate reported success. All three targets are sections in that same file:
[[Images]] already existed, [[Fonts]] is added to match it, and
[[text-decoration]] was already there and already used by a sibling reference.
check-guide-xrefs.py now reports any relative href, which after this change has
zero instances. Absolute URLs stay with check-guide-links.py, and root-relative
paths and fragments are excluded. A/B with one link restored: old printed
"Cross-references OK" and exited 0, new reports it and exits 1.
The second is a wrong claim in my own comment. check-guide-structure.py returned
early when an include was followed by ifdef/ifndef/ifeval/endif, on the reasoning
that "a preprocessor directive is not content and cannot absorb a paragraph".
True, and irrelevant: asciidoctor REMOVES the directive during preprocessing, so
it does not separate anything either. Measured -- with `ifdef::backend-html5[]`
between an include ending in prose and a following heading, the render contains
zero heading tags and the paragraph reads "...preferred tools. === A parent-local
heading". The rule now scans past directive lines to the first line that survives
preprocessing. A/B on that construct: old passed it, new reports it.
Note the rendered-title check cannot cover this case either, because a
parent-local heading is not the first heading of any included file.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:f9e50cb8cc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-links.py
Comment threadscripts/developer-guide/check-guide-xrefs.py
… has a limit
The first finding was live: the link ratchet held a working link.
docs/website/functions/[[path]].js runs after context.next() has already 404ed
and then redirects anything under /files/ or /demos/ to
download.codenameone.com. Those paths are served, so the model was missing a
whole class of route and had banked a real one -- /files/iOS_UI-Kit.psd -- as
broken. The destination is off-site, which puts it in the same bucket as every
other off-site redirect: reachable, not verifiable from this repository. The
rules are appended AFTER the _redirects rules because the function is a fallback
and the first matching rule wins, mirroring how the host evaluates. The baseline
shrinks 37 -> 36, and a new /files/ link is now accepted rather than failing CI.
The second is a real limit of the PDF surrogate, and one the command line cannot
remove. Setting backend-pdf makes ifndef::backend-pdf[] content disappear and
ifdef::backend-pdf[] content appear -- verified, and that is the only form this
guide uses. It does not undefine backend-html5: the HTML converter sets that
itself, after command-line attributes are applied, so even `-a backend-html5!`
leaves it defined. Measured both ways. Content guarded on the HTML backend would
therefore survive into the surrogate render and could satisfy a PDF-only
reference that the real asciidoctor-pdf build leaves dangling.
The guide has no such conditional, so rather than model a construct that is not
there -- or drive asciidoctor-pdf and try to read anchors back out of a PDF --
the checker refuses one if it appears, naming the alternative the rest of the
guide already uses. Probed: planting an ifdef::backend-html5[] block aborts the
check with that message.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:6a8963d02a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-structure.py Outdated
…side one
Both scanners matched a delimiter of exactly four characters, so a block opened
with ----- was invisible to them and its contents were scanned as live markup.
The guide has 20 such lines -- eight in Desktop-Integration, six in
Working-With-iOS, six in appendix_goal_generate_archetype -- every one an
ordinary [source] block wrapping an include::. validate-guide-snippets.py already
accepted -{4,}; these two had drifted from it.
Nothing is misread today, because those includes name .java, .xml and .properties
files and the reachability walk skips non-asciidoc targets. The failure mode is
still the interesting one though: it is a FALSE POSITIVE generator, not a missed
defect. A/B with each construct planted inside a five-dash block:
a displayed include::Working-with-UWP.asciidoc[]
old reported a swallowed-heading error against markup that only appears
on the page as text
new clean
a displayed "The wrapper looks like this:" followed by blank lines
old reported it as a missing code block
new clean
Both now track the delimiter that opened the block and close on one of the same
character AND length, which is what asciidoctor does -- so a four-dash line
inside a five-dash block is content rather than the close. The set stays limited
to literal blocks: an example (====) or sidebar (****) block contains live
markup, so headings and includes inside one are real and must keep counting.
Baselines unchanged: 119 included documents, 402 holes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:57212b7f43

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-structure.py
Comment threadscripts/developer-guide/check-guide-xrefs.py
shai-almogand others added 2 commits September 2, 2026 18:21
* Guide figures render with a bundled font instead of the host's
None of the 24 generated figures could be reproduced outside CI. Running the
generator on a Mac reproduces 0 of 24 byte-for-byte, with 3-20% of pixels
differing. Two consecutive local runs are byte-identical, so the generator is
deterministic on one host; the variable is the font.
`PreAdvancedThemingScreenshots` styled with `Font.createSystemFont`, which
resolves through `JavaSEPort.fontFaceSystem` -- "Arial" on macOS and Linux
alike. Arial exists on a developer's Mac and not on a stock CI runner, so AWT
silently substitutes and every glyph changes. The differences are exactly that:
for `flow-layout.png` every differing pixel sits in y 10-130, the title and
label rows, while the colored blocks below match to the pixel.
So the byte-exact gate has only ever been green because CI both generates and
verifies. A developer regenerating locally could not match it, and at the scale
the guide's remaining ~260 app screenshots would need, that is untenable.
The figures now load `native:MainRegular`, which `JavaSEPort.loadTrueTypeFont`
reads from `/com/codename1/impl/javase/Roboto-Medium.ttf` on the classpath
rather than from an installed-font lookup. This is also what the project's font
rule requires everywhere: never `createSystemFont`, always the `native:` scheme.
A null return refuses loudly rather than falling back to a host font, because a
silent fallback would restore the exact non-determinism this removes.
The committed baselines are regenerated from a Mac. CI byte-compares them on
Linux, so the check either passes -- proving host independence rather than
asserting it -- or fails and says so immediately.
Composition, dimensions and colours are unchanged; only the typeface moves.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Pin the font on every component, not just the ones that get block colours
CI reported one stale figure out of 24: `guibuilder-2-insets-3.png`, the only
one containing a `TextField`. The other 23 matched Linux byte for byte, so the
bundled-font change worked -- it just did not reach far enough.
`applyBlockStyleToContent` styled `Label` and `Button`. Everything else kept the
theme's default font, which resolves through the host, so the text field and its
hint were still host-dependent. Enumerating the types that carry text would have
left the next one added broken the same way, so the walk now pins the face on
every component it visits, plus the hint label, which is painted by a `Label`
that is not in the component tree and so is never reached by the walk.
The first attempt used `BLOCK_FONT` for this and regressed the figure: at 29px
against the theme default's 13px the field grew and squeezed "Submit" down to a
clipped sliver. `FIELD_FONT` is sized to reproduce the original height, so the
composition is unchanged and only the typeface moves.
Verified locally by rendering twice, once with `JavaSEPort.setFontFaces` pointed
at a family that does not exist -- which is what a machine without Arial looks
like to the port. All 24 come out byte-identical, so nothing in these figures
reads an installed font any more.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Publish what the runner actually rendered when a screenshot fails
"Committed screenshot is stale: <name>" names the file and nothing else, so
there is no way to tell a real regression from an environment difference
without adding a debugging round trip to CI. The generated directory is now
uploaded as an artifact when the step fails.
Needed immediately: one figure still differs between a Mac and the runner after
the font fix, and host fonts, JDK version and working directory have each been
ruled out locally.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Pin MigLayout's platform, and stop demanding byte equality of a glyph edge
The artifact step paid for itself immediately. Downloading what the runner
rendered showed two figures differing, not the one the gate reported -- it exits
on the first mismatch -- and the two had nothing in common.
`mig-layout.png` differed by 12.8% of its pixels, a real layout shift.
MigLayout takes its default gaps from `PlatformDefaults`, which reads
`System.getProperty("os.name")` and answers MAC_OSX, GNOME or WINDOWS_XP, each
with different spacing. The figure was rendering with macOS gaps on a Mac and
GNOME gaps on the runner. Pinning the platform fixes it: with the pin, 23 of the
24 figures now match the runner's own output byte for byte.
`layered-layout.png` differed by 173 pixels, 0.113%, inside a 25x25 box. That
one is not fixable. Measured against the runner's bytes, the material glyph
lands at exactly the same size and the same origin -- a 55x49 bounding box --
and differs only in antialiased edge coverage, 946 fully-white pixels against
916. Java2D rasterizes the same glyph, from the same bundled font, at the same
size, slightly differently on the two platforms. Demanding byte equality there
would mean deleting legitimate content from the figure or carrying a
permanently red check.
So the comparison moves from `cmp -s` to a comparer that still requires byte
equality by default and accepts a bounded difference only where a figure carries
a `.tolerance` sidecar explaining itself, in the same key=value shape the CN1SS
suites already use. The area bound does the work: a per-pixel delta that large is
meaningless on its own, since a glyph edge flips between white and the block
behind it, but a regression that changed the icon would move far more than 0.3%
of the image.
Verified by running the comparer with the runner's own output against the
committed figures -- which is exactly what CI will do -- and by four probes: an
untoleranced figure that differs fails, the toleranced figure fails when changed
beyond its budget, a missing figure fails the count, and a clean run passes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Guard the iOS font branch, and stop the comparator gating itself out
Two review findings on this PR.
The first says `native:MainRegular` does not give host-independent output,
because `loadTrueTypeFont` resolves native fonts to the first installed SF or
Helvetica family before reaching the bundled Roboto. The branch is real, but it
is reached only when `isIOS` is set, which `loadSkinFile` does for a skin whose
systemFontFamily contains "helvetica" -- and this generator never loads a skin.
The measurement agrees: figures rendered on a Mac match the Linux runner byte
for byte, which could not happen if one side were resolving Helvetica Neue and
the other Roboto.
So the conclusion does not hold today, but the risk is real for tomorrow: a
change that loads a skin here would put host fonts back into the output with no
other symptom. The generator now refuses to run under an iOS platform, and says
why, rather than leaving that to a comment nobody reads.
The second finding is straightforwardly right and is the more serious of the
two. `on.pull_request.paths` triggers on `scripts/developer-guide/**`, but the
`Determine changed components` filter named only two scripts, so a pull request
touching any other script here started the workflow with `docs` false and
skipped the steps that script governs. A change to compare-screenshots.py could
have merged without the screenshot check ever running it -- a gate that skips
itself. The filter now covers the whole directory, which also removes the
two-copies-drift the surrounding comment already warns about.
Verified the guard changes no output: all 24 figures are unchanged, and the
comparer still passes against the runner's own bytes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Count every changed pixel toward the area budget, not just the loud ones
The comparator incremented its mismatch counter only for pixels whose channel
delta EXCEEDED maxChannelDelta, which is what the CN1SS comparator does. With a
sidecar written the way this one was -- a large delta bound paired with a small
area bound -- that leaves an unbounded hole, and review gave the exploit in the
figures' own palette: recolouring the green #06a806 to #a608a6 moves every
channel by exactly 160, so with maxChannelDelta=160 not one pixel is counted and
a dramatically different image reports zero mismatches.
Reproduced it before fixing: the recolour changes 4191 pixels, 2.73% of the
image, at a worst delta of exactly 160 -- and passed.
The two bounds are now independent. maxMismatchPercent limits how much of the
image may change at all, counting every differing pixel; maxChannelDelta caps how
far any single pixel may move. The measured legitimate noise -- 173 pixels,
0.113%, worst delta 141 -- still passes, the recolour now fails on area, and a
five-pixel solid overwrite fails on delta.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Correct the sidecar's rationale to match how the bounds now work
The previous commit changed the comparator so every differing pixel counts
toward the area budget and the channel delta is a separate ceiling. The sidecar
still explained the old behaviour -- that the area bound did the work and the
delta was meaningless on its own -- which is now wrong in a file whose whole
purpose is to justify the numbers beside it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Compare figures in RGBA, because they have an alpha channel
The comparator converted both images to RGB before counting differences. The
committed figures are genuine RGBA PNGs -- four channels, currently opaque
everywhere -- so that dropped a real channel, and any change confined to alpha
was invisible to the tolerance path. A regression that turned the whole figure
transparent while leaving every colour channel intact reported zero changed
pixels and passed.
Byte equality, which every figure without a sidecar is still held to, always
caught this. Only a figure carrying a sidecar could reach the weakened path, so
today the exposure was one image -- but that image is exactly the one whose
comparison is relaxed.
Verified: making layered-layout.png fully transparent with its RGB channels
untouched now reports 100% of pixels changed, the real runner output still
passes, and the recolour and untoleranced-difference probes still fail as they
did.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…page
Two findings, one of them a false positive I introduced last round.
Comment blocks. Asciidoctor drops a //// block entirely, so an include inside
one does not put its target in the book and prose inside one promises the reader
nothing -- but both scanners read straight through. //// joins the fence set in
each. A/B with each construct planted inside a comment block: a displayed
include:: made the structure check fail, and a promising sentence made the
missing-block check fail; both are clean now.
Note this is deliberately NOT symmetric with check-guide-links.py, which still
reads URLs inside comments. The reasons differ and the code says so: a dead link
commented out is still dead debt in the source, and letting the ratchet shrink
for it would make "comment the line out" a way to silence that gate. Reachability
is the opposite -- calling a commented-out include reachable states something
about the book that is simply untrue.
Packaged assets. The relative-href rule added last round was too broad: the HTML
packaging step copies every subdirectory of docs/developer-guide next to
developer-guide.html, so link:img/example.png[] resolves for a reader who opens
the zip, and the gate would have rejected it. A relative href that names an
existing file under the guide directory is now allowed. sketch/ is not, because
that is the one directory the packaging loop skips.
Probed all four ways, since an exemption is only worth having if it still
rejects:
link:img/flow-layout.png[] exit 0 (real, packaged)
link:img/does-not-exist.png[] exit 1
link:sketch/whatever.svg[] exit 1 (not packaged)
link:Supported-Properties[] exit 1 (the wiki-era shape)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:a6bd5197dc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-xrefs.py Outdated
Comment threadscripts/developer-guide/check-guide-xrefs.py
shai-almogand others added 2 commits September 2, 2026 18:35
Both findings are defects in the two rules added in the previous commit.
packaged_asset() asked whether the file exists here, which is not the question. A
link only works if the published output carries it, and the two outputs filter
differently:
* the HTML archive copies only the SUBDIRECTORIES of docs/developer-guide next
to developer-guide.html and skips sketch, so a root-level file is never in the
zip however much it exists in the tree;
* the website rsync excludes sketch/, *.asciidoc and *.adoc, so a source file
nested inside a packaged directory still does not reach the site.
So link:Introduction.asciidoc[] was being exempted and readers would follow it to
nothing. Both filters are now reproduced. The permissive direction is the
expensive one here -- it suppresses a finding for a link nobody can follow --
so all four cases are probed:
img/flow-layout.png exit 0 in both outputs
Introduction.asciidoc exit 1 in neither
img/does-not-exist.png exit 1
sketch/foo.svg exit 1 excluded by both
The backend-conditional rejection scanned raw lines, so a source block DISPLAYING
`ifdef::backend-html5[]` as example text aborted the whole gate. A displayed
directive takes no part in preprocessing. It now tracks literal and comment block
delimiters the way the other guide scanners do. A/B: displayed inside a source
block went 1 -> 0, while a genuinely active conditional still exits 1.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…18 statistics (#5671)
* The Introduction taught a dead IDE menu, an iTunes walkthrough and 2018 statistics
Six corrections to the chapter a new developer reads first:
* The JavaScript target was described twice and contradicted itself: the
overview said TeaVM did the translation, the port section said ParparVM with
TeaVM as a fallback. The port section is right -- `javascript.port` is
declared with `parparvm` as its default and `teavm` documented as "the
original builder as a compatibility fallback".
* `http://teavm.org:[TeaVM-based builder]` -- the stray colon made the macro a
bare host, and the site serves TLS. Removed from the link ratchet, which now
stands at 37.
* Bitcode was cited as something ParparVM absorbed without modification. Apple
has since withdrawn bitcode, which makes the point better than the arrival
did, so the sentence now names both moves.
* The device-fragmentation section rested on two 2018 Android share numbers,
followed by a sentence conceding they would be stale on arrival. The
structural claim survives without them.
* The iOS developer fee was dated with "for 10 years at the time of this
writing".
* The UDID instructions walked the reader through iTunes, with a screenshot of
iTunes on iOS 9.3.5 that also exposed a real device serial and UDID. iTunes
has not existed on macOS since Catalina, and the signing chapter already gives
the current answer, so this now points there.
* The device build was "a right click away" via the IDE plugin, illustrated by a
menu still offering Blackberry, J2ME and Windows Phone builds. Replaced with
the `cn1:buildAndroid` and `cn1:buildIos` goals, taken from the mojo names.
Both screenshots are deleted, so find_unused_images.py stays satisfied.
Verified: asciidoctor at --failure-level WARN, Vale at suggestion level,
LanguageTool (status ok, 0 matches, run under JDK 17 rather than the JDK 8
false green), paragraph capitalization, snippets, xrefs, structure, links and
missing-code-blocks all clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* "-pl common" made a device build a no-op that reported success
Review caught the command I had just written. AbstractBuildWrapperMojo.execute()
opens with:
if (!project.isExecutionRoot()) {
getLog().info("Skipping execution for non-root project");
return;
}
and BuildAndroidMojo, BuildIosMojo and BuildWin32Mojo all inherit it unchanged.
Under `-pl common` the selected module is not the execution root, so the goal
logs that line and stops. The design is deliberate -- the wrapper re-invokes
Maven on the root pom itself (`request.setPomFile(new File("pom.xml"))`) -- so
naming a module both skips the wrapper and defeats its purpose.
Measured rather than reasoned, against scripts/hellocodenameone:
mvn -pl common ...:buildWin32 -> "Skipping execution for non-root project"
BUILD SUCCESS, nothing built
mvn ...:buildWin32 -> no skip line, proceeds into the reactor
A silent success is the worst failure mode for a getting-started instruction, so
the Introduction now says where to run it and warns about the module form
explicitly.
Working-With-Windows carried the same `-pl common` shape and is fixed with it --
it is where I copied the form from, so leaving it would reintroduce the defect
the next time someone follows the pattern.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* The Windows build wrappers named a platform no profile answers to
Review checked the command this PR documents and found it still builds nothing.
The wrapper passes `codename1.platform` to its nested Maven run, and that
property is what activates the module profile in a generated project's root pom.
The profile is `win`, matching the value the win module itself declares:
<profile><id>win</id>
<activation><property>
<name>codename1.platform</name><value>win</value>
</property></activation>
<modules><module>win</module></modules>
BuildWin32Mojo and BuildWindowsDeviceMojo both passed "windows", which matches no
profile at all. Nothing else in the plugin reads the platform as "windows", so
the value was simply inert: the win module never joined the reactor and the
nested build reported success having produced no Windows binary. Every other
wrapper already agrees with its profile -- android, ios, javascript, linux and
javase all match -- so these two were the only ones out of step.
Verified by A/B against scripts/hellocodenameone, reading the NESTED reactor
rather than the outer one:
before hellocodenameone, -common, -javase <- no win module
after hellocodenameone, -common, -win
The build TARGET is a separate namespace and stays "windows-device"
(Executor.BUILD_TARGET_WINDOWS_NATIVE), which is correct.
SpotBugs over codenameone-maven-plugin regenerated: 0 findings.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* The primary Win32 command had both defects the convenience goal just lost
Fixing BuildWin32Mojo made the sentence beside this snippet false: the goal now
submits a build, while the command the chapter leads with still could not. It
carried both defects at once --
mvn -pl common package -Dcodename1.platform=windows ...
-- `-pl common` builds that module instead of the reactor, so the win module is
never reached, and `windows` matches no profile even when the reactor is whole.
The two mistakes hid each other: with only the module selected, the platform
value had nothing left to activate.
Corrected to what the wrapper actually runs, read off AbstractBuildWrapperMojo:
goal `package`, `codename1.platform=win`, `codename1.buildTarget=windows-device`,
from the project root.
A/B against scripts/hellocodenameone, reading the reactor, which Maven prints
before it compiles:
old no win module in the reactor at all
new hellocodenameone, -common, -win
Both invocations then fail identically on this machine with "invalid target
release: 17", because tools/env.sh pins JDK 8 while the demo targets 17. That is
environmental and equal on both sides, so it does not affect the comparison.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@shai-almog
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Gates for the developer guide defects Asciidoctor cannot see - #5669

Open
shai-almog wants to merge 17 commits into
masterfrom
devguide-guards
Open

Gates for the developer guide defects Asciidoctor cannot see#5669
shai-almog wants to merge 17 commits into
masterfrom
devguide-guards

Conversation

@shai-almog

Copy link
Copy Markdown
Collaborator

Split out of #5664 so the structural repairs there stop waiting on tooling review. Stacked on it, because these checks are written against a guide that #5664 fixes — run them on master and several fail by design.

Four checks. Each was verified to fail when the defect it describes is reintroduced, and to pass again when it is reverted.

CheckCatchesAsciidoctor's behaviour
check-guide-structure.pya document in the tree but not in the book; a chapter that silently becomes a part, a subsection, or a duplicate; an included chapter whose title does not survive into the rendered outputsilent
check-guide-xrefs.pyan internal link that resolves to nothing, and one that renders as a bare [some-id]silent — a reference with link text renders as an ordinary link to nowhere
check-missing-code-blocks.pyprose that promises a listing where a hole followsnot its job
check-guide-links.pylinks checked against the paths the site actually servesnot its job

How the link checker decides

It does not carry a list of good links. It derives what the site serves and follows the rules to what they produce:

  • _redirects sources compiled to patterns, splat and :placeholder included, then followed to their destination rather than accepted for matching. /*.html matches every root-level .html path there is, so a match proves nothing.
  • Hugo routes from front matter — url, slug, aliases — because 1055 of the 1262 content pages override their route with slug, and deriving it from the filename both invents routes and rejects real ones.
  • Draft and future-dated pages excluded, since Hugo defaults buildDrafts and buildFuture to false.
  • Static assets enumerated as files; a directory without an index.html is not a route.
  • /developer-guide/ derived from what scripts/website/build.sh rsyncs. Only /javadoc/ stays exempt, because it is produced from the framework sources and cannot be enumerated here.

Ratchets

check-missing-code-blocks.py records 406 existing holes and check-guide-links.py38 bad links. Both are multisets — one line per occurrence, because a file naming the same bad URL twice has two problems.

Both may only shrink. A fixed entry that is still baselined fails, so the ratchet actually banks its fixes; growing one requires --allow-new, so recording new debt is deliberate rather than a side effect of regenerating.

Where they run

developer-guide-docs.yml runs all four. check-guide-links.py also runs from website-docs.yml, which triggers on docs/website/** — a page renamed or re-slugged there breaks the guide, and putting those paths into the guide workflow would run the whole guide build, maven install and screenshot generation included, for every blog post the daily publisher pushes.

The docs paths filter now covers scripts/developer-guide/** rather than two named files, because a change to a script not in that filter started the workflow while skipping the steps that script governs.

@chatgpt-codex-connector

chatgpt-codex-connectorBot commented Sep 2, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

ReviewStatusCommitReview trigger
📝 Code Review🔄 Running since 2026-09-02T15:38:41.729150Za9c0b73New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:2da41dd8c7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-links.py Outdated
Comment threadscripts/developer-guide/check-guide-structure.py

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:28f950b311

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-links.py

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:21e513112f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-links.py Outdated
@github-actions

Copy link
Copy Markdown
Contributor

Cloudflare Preview

@github-actions

github-actionsBot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Developer Guide build artifacts are available for download from this workflow run:

Developer Guide quality checks:

  • AsciiDoc linter: No issues found (report)
  • Vale: No alerts found (report)
  • Paragraph capitalization: No paragraph capitalization issues (report)
  • LanguageTool: No grammar matches (report)
  • Image references: No unused images detected (report)

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:6d0560cc05

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-structure.py
Comment threadscripts/developer-guide/check-guide-links.py

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:b96d1321a1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-links.py Outdated
Comment threadscripts/developer-guide/check-guide-links.py
@shai-almog
shai-almogforce-pushed the devguide-structure-and-guards branch from 6108cad to e113f04CompareSeptember 2, 2026 11:57

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:d82c0ea525

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-structure.py

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:42189c33d9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-links.py
Comment threadscripts/developer-guide/check-guide-links.py

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:89e588ddc8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-missing-code-blocks.py Outdated
Comment threadscripts/developer-guide/check-guide-xrefs.py Outdated

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:b88150d821

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-xrefs.py
Comment threadscripts/developer-guide/check-guide-structure.py Outdated
Base automatically changed from devguide-structure-and-guards to masterSeptember 2, 2026 13:19
shai-almogand others added 10 commits September 2, 2026 16:20
Four checks, split out of the structural repairs they were written alongside so
each can be reviewed on its own terms. Every one was verified to fail when the
defect it describes is reintroduced, and to pass again when it is reverted.
check-guide-structure.py -- every document is in the book or declared out of it;
no chapter silently becomes a part, a subsection, or a duplicate; and, by
rendering the book and reading its headings back, every included chapter's title
survives into the output. That last check is what would have caught a whole
chapter rendering as subsections of the one before it.
check-guide-xrefs.py -- every internal link resolves, and none renders as a bare
"[some-id]". Asciidoctor reports neither: a reference carrying link text renders
as an ordinary link to nowhere.
check-missing-code-blocks.py -- prose that promises a listing where a hole
follows. 406 exist, from the snippet extraction in bbdc605, recoverable from
bbdc605~1.
check-guide-links.py -- links checked against the paths the site actually
serves, derived from _redirects and the Hugo content tree and followed through
every redirect rule to what it produces, rather than accepted for matching one.
The two ratchets may only shrink; growing one needs --allow-new, so recording
debt is deliberate rather than a side effect of regenerating.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The stacked content change restored the video capture constraints examples, so
their baseline entries went stale and the ratchet refused to pass until they
were removed. 406 to 402.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The outcome check compared counts of normalized headings at any depth, which a
collision satisfies: "Analytics" is a chapter and also a subsection of Commerce,
so the rendered book contains it twice. If the chapter were swallowed, the
subsection kept the count at one and the check -- the central safeguard of this
change -- passed. Measured on the current book: analytics appears twice at any
level and once at chapter level; "getting started" four times at any level and
never at chapter level.
Manifest entries are now counted against chapter-level headings only. Nested
fragments still use the any-level count, because their depth is whatever their
parent gives them.
Second finding: resolves() tried every matching redirect rule and passed if any
chain worked. The host applies the FIRST match and stops, so a link matching an
early rule that leads somewhere deleted would have passed on the strength of a
later rule the reader never reaches. It now returns the first match's result. No
guide link changes verdict today; the point is that the model matches the
behaviour it claims to model.
Also declined, with the reasoning left in the code rather than a review thread:
adding Markdown's three-backtick fence to the literal-block list. The guide
contains none, and validate-guide-snippets.py requires every listing to be
[source,LANG] with a bare include:: inside ---- delimiters, so such a block
would fail that gate before reaching this one.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The pattern required empty brackets, which is right for ifdef and ifndef -- with
content they are the single-line form and guard only that line -- but wrong for
ifeval, which has no single-line form and always carries its expression in the
brackets. So the directive was listed and never recognised.
Latent: the manifest contains no ifeval, and its only conditionals are two
single-line ifdef attribute assignments, which are still correctly treated as
not opening a block. Fixed because a pattern that names three directives and
handles two is a bug in what the code claims, not a missing feature.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review asked for AsciiDoc comment-state tracking so that a URL inside a `//`
line or a `////` block is skipped. Declining, and recording it where the next
reader of this loop will look rather than in a review thread nobody reads.
The guide has 120 files, zero commented-out URLs and zero `////` blocks, so the
tracking would govern nothing that exists. It would also weaken the gate: with
comments skipped, commenting a line out makes its finding disappear and lets the
ratchet shrink, banking a "fix" while the dead link stays in the source waiting
to be uncommented. Deleting the link is the fix.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review asked for fragment validation on same-site URLs. Measuring first: the
guide holds 42 same-site URLs with a fragment, and 38 are /javadoc/ -- the one
tree this script cannot enumerate, because it is generated from the framework
sources at build time. The other four all pointed at
`developer-guide.html#<anchor>`: this book, linking into its own body.
Those are the same defect as the fifteen `/manual/*.html` deep links already
converted in this stack, missed because they use a different path. They send a
reader out to the web to fetch the page they are reading, they do not work in
the PDF, and a renamed section breaks them silently -- the anchor lives in the
rendered book, so no amount of site-path checking can see it.
So the fix is not to validate the fragment, it is to stop writing the link:
all four become `<<anchor,text>>` xrefs, which check-guide-xrefs.py already
resolves against the rendered anchors. All three targets exist today, so this
repairs no live 404 -- it moves four unguarded links under a gate.
The rule closes the class rather than the four instances: any codenameone.com
link whose path is one of this book's own routes and which carries a fragment is
now reported. Verified non-vacuous by restoring one of the four and watching it
fail, then reverting.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both findings describe constructs the guide does not contain, so neither is a
live defect. Both are real holes in the checkers though, and each closes for a
few lines, so they close rather than getting argued with.
Conditional includes (check-guide-structure.py). A manifest entry inside an
ifdef/ifeval was skipped by the duplicate count -- correctly, since the same
chapter under two exclusive branches is one chapter in the output -- and also by
the rendered-title check, since only one branch renders. Skipping both means a
chapter included twice inside a single ACTIVE branch would pass silently. Rather
than teach the checker to distinguish exclusive branches for a construct that
does not exist (measured: zero include:: lines sit inside a conditional anywhere
in the guide), a conditional manifest entry is now refused outright, with a
message saying what to extend if one is ever wanted.
Trailing slashes on file paths (check-guide-links.py). Site paths were compared
with the trailing slash stripped, so a link to `/x.jar/` was validated against
`/x.jar`. The reviewer's premise checks out: _redirects declares the two forms as
separate routes and spells both out where both work, 32 such pairs. Preserving
the slash everywhere would break the many legitimate directory links, so the
narrow case is reported instead -- a last path segment containing a dot, wearing
a trailing slash. Directory routes such as /blog/ and /javadoc/com/codename1/io/
have no dot in the last segment and are untouched.
Both rules verified non-vacuous by introducing an instance and reading the real
exit status rather than a pipeline's: structure and links each exit 1 on the
probe and 0 once it is reverted.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review is right that the rendered-title check has slack: it counts normalized
titles across the whole book, so a nested fragment losing its heading can be
covered by an identical heading elsewhere. Confirmed rather than accepted --
removing the leveloffset from both Maven includes takes "Getting started" from 4
rendered headings to 3, while only one file declares it, so `3 >= 1` and the gate
passed a genuinely swallowed section.
Rather than give nested fragments a rendered identity, which the HTML does not
carry, this checks the cause. The spacing rule already existed but ran only on
the root manifest, and its condition was "a blank line follows". Both were wrong.
Reproduced minimally to find the real rule -- a heading is absorbed only when ALL
of these hold:
* the include is followed immediately by content, and
* neither this include nor the following one carries leveloffset (asciidoctor
brackets the content with :leveloffset: attribute entries, and an attribute
entry closes the paragraph -- the one on the FOLLOWING include lands between
the paragraph and the heading, so it protects just as well), and
* the included file does not end on a blank line, and
* its last line is ordinary paragraph text -- a delimiter, table row, heading,
attribute entry or comment all close the paragraph. That is why
_generated-build-hints.adoc, which ends on "|===", does not eat the
"Versioned builds" heading directly after it.
Against the real book, all four combinations now agree with what asciidoctor
actually renders:
both leveloffset 4 headings renders ok gate passes
only current 4 headings renders ok gate passes
only following 4 headings renders ok gate passes
neither 3 headings SWALLOWED gate fails
An earlier draft exempted only the current include and failed the third row --
a false positive on markup that renders correctly. Every guide include is now
checked, not just the manifest's: measured across all 120 files, this leaves zero
findings, because the five adjacent includes in Maven-Project-Workflow.asciidoc
are protected by leveloffset.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… the rest
Three link findings, and only the first was a live defect -- in the rule I added
one commit earlier.
The trailing-slash rule was unconditional, so it reported "/videos.html/" as
broken even though _redirects declares that exact source and the site serves it.
The rules here all compile slash-insensitively ("^...$/?"), which is right for
matching but discards the distinction the site actually draws, so the raw sources
are now collected alongside them and a slashed file path is reported only when
_redirects does not spell it out. Probed both ways: "/videos.html/" passes,
"/files/CodenameOneBuildClient.jar/" fails.
Hugo route overrides. hugo.toml sets neither [permalinks] nor uglyURLs today, so
deriving a route as "section path + slug" is currently correct; if either
appears, every route underneath moves and this would go on accepting links to
paths Hugo no longer publishes. Modelling a configuration that is not there would
be guesswork, and reading the built public/ tree makes a local run depend on a
tree that may be stale or absent. So it now notices instead: either key present
aborts the check with what to do about it. Probed with each key in turn.
Protocol-relative links and fragments on ordinary same-site pages are declined,
with the measurements recorded in the code rather than in a review thread nobody
reads. The guide has no `link://` macro at all, and its one bare "//host/path" is
a JavaScript string inside a source block, so widening URL_RE to match "//" would
start reporting code as a broken link. And of the 42 same-site URLs carrying a
fragment, 38 are /javadoc/ -- generated at build time, exempt for that reason --
while the other four pointed into this book and are now xrefs, which
check-guide-xrefs.py resolves against the rendered anchors. Nothing is left that
this script could check without building the Hugo site.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ting
Two more holes in the guards. Neither has an instance in the guide today, and
both close cheaply enough that arguing about them would cost more.
check-guide-xrefs.py rendered the book once, with the default backend, so
anything inside ifdef::backend-pdf[] was dropped before a single reference in it
could be examined -- while the workflow publishes an asciidoctor-pdf build from
the same source. Native-Themes.asciidoc already has two such branches. It now
renders twice, the second time with -a backend-pdf, which selects exactly the
content the PDF build includes without needing asciidoctor-pdf here. The two runs
are checked SEPARATELY rather than pooled: an anchor that exists only in the HTML
branch must not satisfy a reference made in the PDF branch. Findings name the
branch they came from.
A/B with a dangling xref planted inside the backend-pdf branch of
Native-Themes.asciidoc:
old "Cross-references OK: 1741 anchors" -- exit 0
new "Native-Themes.asciidoc:506: <<definitely-not-a-real-anchor>> ...
(1 reference(s), pdf render)" -- exit 1
check-missing-code-blocks.py accepted any `[attribute]` line as the start of the
promised block, so an admonition standing where a listing used to be hid the
hole. An admonition is prose and can never be the listing a sentence promised, so
the five names are excluded. Excluded by name rather than by whitelisting the
kinds that ARE code: measured, the bracket lines legitimately answering a
promising sentence already span [source] (582), [cols=...] and [options=...] (56),
[listing] (3), [quote] (2) and an anchored image, and a whitelist would report the
next kind nobody anticipated.
A/B with each block planted after a promising sentence, using the real signature
of a removed listing -- the two blank lines it leaves behind:
[NOTE] old: none new new: reported as a hole
[source,java] old: none new new: none new
The baseline is unchanged at 402, because the guide currently has no admonition
sitting in a listing's place.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…separator
Two findings, and this time the first one is live.
The rendered book contains exactly three relative hrefs, and all three are
broken: css.asciidoc carried link:Images[], link:Fonts[] and
link:Supported-Properties#text-decoration[] over from the wiki this guide
replaced, naming pages that were never carried across with them. The guide
renders as a single page, so a relative href resolves against wherever that page
is served and reaches nothing that ships with it -- the reader gets a 404 while
every gate reported success. All three targets are sections in that same file:
[[Images]] already existed, [[Fonts]] is added to match it, and
[[text-decoration]] was already there and already used by a sibling reference.
check-guide-xrefs.py now reports any relative href, which after this change has
zero instances. Absolute URLs stay with check-guide-links.py, and root-relative
paths and fragments are excluded. A/B with one link restored: old printed
"Cross-references OK" and exited 0, new reports it and exits 1.
The second is a wrong claim in my own comment. check-guide-structure.py returned
early when an include was followed by ifdef/ifndef/ifeval/endif, on the reasoning
that "a preprocessor directive is not content and cannot absorb a paragraph".
True, and irrelevant: asciidoctor REMOVES the directive during preprocessing, so
it does not separate anything either. Measured -- with `ifdef::backend-html5[]`
between an include ending in prose and a following heading, the render contains
zero heading tags and the paragraph reads "...preferred tools. === A parent-local
heading". The rule now scans past directive lines to the first line that survives
preprocessing. A/B on that construct: old passed it, new reports it.
Note the rendered-title check cannot cover this case either, because a
parent-local heading is not the first heading of any included file.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:f9e50cb8cc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-links.py
Comment threadscripts/developer-guide/check-guide-xrefs.py
… has a limit
The first finding was live: the link ratchet held a working link.
docs/website/functions/[[path]].js runs after context.next() has already 404ed
and then redirects anything under /files/ or /demos/ to
download.codenameone.com. Those paths are served, so the model was missing a
whole class of route and had banked a real one -- /files/iOS_UI-Kit.psd -- as
broken. The destination is off-site, which puts it in the same bucket as every
other off-site redirect: reachable, not verifiable from this repository. The
rules are appended AFTER the _redirects rules because the function is a fallback
and the first matching rule wins, mirroring how the host evaluates. The baseline
shrinks 37 -> 36, and a new /files/ link is now accepted rather than failing CI.
The second is a real limit of the PDF surrogate, and one the command line cannot
remove. Setting backend-pdf makes ifndef::backend-pdf[] content disappear and
ifdef::backend-pdf[] content appear -- verified, and that is the only form this
guide uses. It does not undefine backend-html5: the HTML converter sets that
itself, after command-line attributes are applied, so even `-a backend-html5!`
leaves it defined. Measured both ways. Content guarded on the HTML backend would
therefore survive into the surrogate render and could satisfy a PDF-only
reference that the real asciidoctor-pdf build leaves dangling.
The guide has no such conditional, so rather than model a construct that is not
there -- or drive asciidoctor-pdf and try to read anchors back out of a PDF --
the checker refuses one if it appears, naming the alternative the rest of the
guide already uses. Probed: planting an ifdef::backend-html5[] block aborts the
check with that message.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:6a8963d02a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-structure.py Outdated
…side one
Both scanners matched a delimiter of exactly four characters, so a block opened
with ----- was invisible to them and its contents were scanned as live markup.
The guide has 20 such lines -- eight in Desktop-Integration, six in
Working-With-iOS, six in appendix_goal_generate_archetype -- every one an
ordinary [source] block wrapping an include::. validate-guide-snippets.py already
accepted -{4,}; these two had drifted from it.
Nothing is misread today, because those includes name .java, .xml and .properties
files and the reachability walk skips non-asciidoc targets. The failure mode is
still the interesting one though: it is a FALSE POSITIVE generator, not a missed
defect. A/B with each construct planted inside a five-dash block:
a displayed include::Working-with-UWP.asciidoc[]
old reported a swallowed-heading error against markup that only appears
on the page as text
new clean
a displayed "The wrapper looks like this:" followed by blank lines
old reported it as a missing code block
new clean
Both now track the delimiter that opened the block and close on one of the same
character AND length, which is what asciidoctor does -- so a four-dash line
inside a five-dash block is content rather than the close. The set stays limited
to literal blocks: an example (====) or sidebar (****) block contains live
markup, so headings and includes inside one are real and must keep counting.
Baselines unchanged: 119 included documents, 402 holes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:57212b7f43

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-structure.py
Comment threadscripts/developer-guide/check-guide-xrefs.py
shai-almogand others added 2 commits September 2, 2026 18:21
* Guide figures render with a bundled font instead of the host's
None of the 24 generated figures could be reproduced outside CI. Running the
generator on a Mac reproduces 0 of 24 byte-for-byte, with 3-20% of pixels
differing. Two consecutive local runs are byte-identical, so the generator is
deterministic on one host; the variable is the font.
`PreAdvancedThemingScreenshots` styled with `Font.createSystemFont`, which
resolves through `JavaSEPort.fontFaceSystem` -- "Arial" on macOS and Linux
alike. Arial exists on a developer's Mac and not on a stock CI runner, so AWT
silently substitutes and every glyph changes. The differences are exactly that:
for `flow-layout.png` every differing pixel sits in y 10-130, the title and
label rows, while the colored blocks below match to the pixel.
So the byte-exact gate has only ever been green because CI both generates and
verifies. A developer regenerating locally could not match it, and at the scale
the guide's remaining ~260 app screenshots would need, that is untenable.
The figures now load `native:MainRegular`, which `JavaSEPort.loadTrueTypeFont`
reads from `/com/codename1/impl/javase/Roboto-Medium.ttf` on the classpath
rather than from an installed-font lookup. This is also what the project's font
rule requires everywhere: never `createSystemFont`, always the `native:` scheme.
A null return refuses loudly rather than falling back to a host font, because a
silent fallback would restore the exact non-determinism this removes.
The committed baselines are regenerated from a Mac. CI byte-compares them on
Linux, so the check either passes -- proving host independence rather than
asserting it -- or fails and says so immediately.
Composition, dimensions and colours are unchanged; only the typeface moves.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Pin the font on every component, not just the ones that get block colours
CI reported one stale figure out of 24: `guibuilder-2-insets-3.png`, the only
one containing a `TextField`. The other 23 matched Linux byte for byte, so the
bundled-font change worked -- it just did not reach far enough.
`applyBlockStyleToContent` styled `Label` and `Button`. Everything else kept the
theme's default font, which resolves through the host, so the text field and its
hint were still host-dependent. Enumerating the types that carry text would have
left the next one added broken the same way, so the walk now pins the face on
every component it visits, plus the hint label, which is painted by a `Label`
that is not in the component tree and so is never reached by the walk.
The first attempt used `BLOCK_FONT` for this and regressed the figure: at 29px
against the theme default's 13px the field grew and squeezed "Submit" down to a
clipped sliver. `FIELD_FONT` is sized to reproduce the original height, so the
composition is unchanged and only the typeface moves.
Verified locally by rendering twice, once with `JavaSEPort.setFontFaces` pointed
at a family that does not exist -- which is what a machine without Arial looks
like to the port. All 24 come out byte-identical, so nothing in these figures
reads an installed font any more.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Publish what the runner actually rendered when a screenshot fails
"Committed screenshot is stale: <name>" names the file and nothing else, so
there is no way to tell a real regression from an environment difference
without adding a debugging round trip to CI. The generated directory is now
uploaded as an artifact when the step fails.
Needed immediately: one figure still differs between a Mac and the runner after
the font fix, and host fonts, JDK version and working directory have each been
ruled out locally.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Pin MigLayout's platform, and stop demanding byte equality of a glyph edge
The artifact step paid for itself immediately. Downloading what the runner
rendered showed two figures differing, not the one the gate reported -- it exits
on the first mismatch -- and the two had nothing in common.
`mig-layout.png` differed by 12.8% of its pixels, a real layout shift.
MigLayout takes its default gaps from `PlatformDefaults`, which reads
`System.getProperty("os.name")` and answers MAC_OSX, GNOME or WINDOWS_XP, each
with different spacing. The figure was rendering with macOS gaps on a Mac and
GNOME gaps on the runner. Pinning the platform fixes it: with the pin, 23 of the
24 figures now match the runner's own output byte for byte.
`layered-layout.png` differed by 173 pixels, 0.113%, inside a 25x25 box. That
one is not fixable. Measured against the runner's bytes, the material glyph
lands at exactly the same size and the same origin -- a 55x49 bounding box --
and differs only in antialiased edge coverage, 946 fully-white pixels against
916. Java2D rasterizes the same glyph, from the same bundled font, at the same
size, slightly differently on the two platforms. Demanding byte equality there
would mean deleting legitimate content from the figure or carrying a
permanently red check.
So the comparison moves from `cmp -s` to a comparer that still requires byte
equality by default and accepts a bounded difference only where a figure carries
a `.tolerance` sidecar explaining itself, in the same key=value shape the CN1SS
suites already use. The area bound does the work: a per-pixel delta that large is
meaningless on its own, since a glyph edge flips between white and the block
behind it, but a regression that changed the icon would move far more than 0.3%
of the image.
Verified by running the comparer with the runner's own output against the
committed figures -- which is exactly what CI will do -- and by four probes: an
untoleranced figure that differs fails, the toleranced figure fails when changed
beyond its budget, a missing figure fails the count, and a clean run passes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Guard the iOS font branch, and stop the comparator gating itself out
Two review findings on this PR.
The first says `native:MainRegular` does not give host-independent output,
because `loadTrueTypeFont` resolves native fonts to the first installed SF or
Helvetica family before reaching the bundled Roboto. The branch is real, but it
is reached only when `isIOS` is set, which `loadSkinFile` does for a skin whose
systemFontFamily contains "helvetica" -- and this generator never loads a skin.
The measurement agrees: figures rendered on a Mac match the Linux runner byte
for byte, which could not happen if one side were resolving Helvetica Neue and
the other Roboto.
So the conclusion does not hold today, but the risk is real for tomorrow: a
change that loads a skin here would put host fonts back into the output with no
other symptom. The generator now refuses to run under an iOS platform, and says
why, rather than leaving that to a comment nobody reads.
The second finding is straightforwardly right and is the more serious of the
two. `on.pull_request.paths` triggers on `scripts/developer-guide/**`, but the
`Determine changed components` filter named only two scripts, so a pull request
touching any other script here started the workflow with `docs` false and
skipped the steps that script governs. A change to compare-screenshots.py could
have merged without the screenshot check ever running it -- a gate that skips
itself. The filter now covers the whole directory, which also removes the
two-copies-drift the surrounding comment already warns about.
Verified the guard changes no output: all 24 figures are unchanged, and the
comparer still passes against the runner's own bytes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Count every changed pixel toward the area budget, not just the loud ones
The comparator incremented its mismatch counter only for pixels whose channel
delta EXCEEDED maxChannelDelta, which is what the CN1SS comparator does. With a
sidecar written the way this one was -- a large delta bound paired with a small
area bound -- that leaves an unbounded hole, and review gave the exploit in the
figures' own palette: recolouring the green #06a806 to #a608a6 moves every
channel by exactly 160, so with maxChannelDelta=160 not one pixel is counted and
a dramatically different image reports zero mismatches.
Reproduced it before fixing: the recolour changes 4191 pixels, 2.73% of the
image, at a worst delta of exactly 160 -- and passed.
The two bounds are now independent. maxMismatchPercent limits how much of the
image may change at all, counting every differing pixel; maxChannelDelta caps how
far any single pixel may move. The measured legitimate noise -- 173 pixels,
0.113%, worst delta 141 -- still passes, the recolour now fails on area, and a
five-pixel solid overwrite fails on delta.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Correct the sidecar's rationale to match how the bounds now work
The previous commit changed the comparator so every differing pixel counts
toward the area budget and the channel delta is a separate ceiling. The sidecar
still explained the old behaviour -- that the area bound did the work and the
delta was meaningless on its own -- which is now wrong in a file whose whole
purpose is to justify the numbers beside it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Compare figures in RGBA, because they have an alpha channel
The comparator converted both images to RGB before counting differences. The
committed figures are genuine RGBA PNGs -- four channels, currently opaque
everywhere -- so that dropped a real channel, and any change confined to alpha
was invisible to the tolerance path. A regression that turned the whole figure
transparent while leaving every colour channel intact reported zero changed
pixels and passed.
Byte equality, which every figure without a sidecar is still held to, always
caught this. Only a figure carrying a sidecar could reach the weakened path, so
today the exposure was one image -- but that image is exactly the one whose
comparison is relaxed.
Verified: making layered-layout.png fully transparent with its RGB channels
untouched now reports 100% of pixels changed, the real runner output still
passes, and the recolour and untoleranced-difference probes still fail as they
did.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…page
Two findings, one of them a false positive I introduced last round.
Comment blocks. Asciidoctor drops a //// block entirely, so an include inside
one does not put its target in the book and prose inside one promises the reader
nothing -- but both scanners read straight through. //// joins the fence set in
each. A/B with each construct planted inside a comment block: a displayed
include:: made the structure check fail, and a promising sentence made the
missing-block check fail; both are clean now.
Note this is deliberately NOT symmetric with check-guide-links.py, which still
reads URLs inside comments. The reasons differ and the code says so: a dead link
commented out is still dead debt in the source, and letting the ratchet shrink
for it would make "comment the line out" a way to silence that gate. Reachability
is the opposite -- calling a commented-out include reachable states something
about the book that is simply untrue.
Packaged assets. The relative-href rule added last round was too broad: the HTML
packaging step copies every subdirectory of docs/developer-guide next to
developer-guide.html, so link:img/example.png[] resolves for a reader who opens
the zip, and the gate would have rejected it. A relative href that names an
existing file under the guide directory is now allowed. sketch/ is not, because
that is the one directory the packaging loop skips.
Probed all four ways, since an exemption is only worth having if it still
rejects:
link:img/flow-layout.png[] exit 0 (real, packaged)
link:img/does-not-exist.png[] exit 1
link:sketch/whatever.svg[] exit 1 (not packaged)
link:Supported-Properties[] exit 1 (the wiki-era shape)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:a6bd5197dc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-xrefs.py Outdated
Comment threadscripts/developer-guide/check-guide-xrefs.py
shai-almogand others added 2 commits September 2, 2026 18:35
Both findings are defects in the two rules added in the previous commit.
packaged_asset() asked whether the file exists here, which is not the question. A
link only works if the published output carries it, and the two outputs filter
differently:
* the HTML archive copies only the SUBDIRECTORIES of docs/developer-guide next
to developer-guide.html and skips sketch, so a root-level file is never in the
zip however much it exists in the tree;
* the website rsync excludes sketch/, *.asciidoc and *.adoc, so a source file
nested inside a packaged directory still does not reach the site.
So link:Introduction.asciidoc[] was being exempted and readers would follow it to
nothing. Both filters are now reproduced. The permissive direction is the
expensive one here -- it suppresses a finding for a link nobody can follow --
so all four cases are probed:
img/flow-layout.png exit 0 in both outputs
Introduction.asciidoc exit 1 in neither
img/does-not-exist.png exit 1
sketch/foo.svg exit 1 excluded by both
The backend-conditional rejection scanned raw lines, so a source block DISPLAYING
`ifdef::backend-html5[]` as example text aborted the whole gate. A displayed
directive takes no part in preprocessing. It now tracks literal and comment block
delimiters the way the other guide scanners do. A/B: displayed inside a source
block went 1 -> 0, while a genuinely active conditional still exits 1.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…18 statistics (#5671)
* The Introduction taught a dead IDE menu, an iTunes walkthrough and 2018 statistics
Six corrections to the chapter a new developer reads first:
* The JavaScript target was described twice and contradicted itself: the
overview said TeaVM did the translation, the port section said ParparVM with
TeaVM as a fallback. The port section is right -- `javascript.port` is
declared with `parparvm` as its default and `teavm` documented as "the
original builder as a compatibility fallback".
* `http://teavm.org:[TeaVM-based builder]` -- the stray colon made the macro a
bare host, and the site serves TLS. Removed from the link ratchet, which now
stands at 37.
* Bitcode was cited as something ParparVM absorbed without modification. Apple
has since withdrawn bitcode, which makes the point better than the arrival
did, so the sentence now names both moves.
* The device-fragmentation section rested on two 2018 Android share numbers,
followed by a sentence conceding they would be stale on arrival. The
structural claim survives without them.
* The iOS developer fee was dated with "for 10 years at the time of this
writing".
* The UDID instructions walked the reader through iTunes, with a screenshot of
iTunes on iOS 9.3.5 that also exposed a real device serial and UDID. iTunes
has not existed on macOS since Catalina, and the signing chapter already gives
the current answer, so this now points there.
* The device build was "a right click away" via the IDE plugin, illustrated by a
menu still offering Blackberry, J2ME and Windows Phone builds. Replaced with
the `cn1:buildAndroid` and `cn1:buildIos` goals, taken from the mojo names.
Both screenshots are deleted, so find_unused_images.py stays satisfied.
Verified: asciidoctor at --failure-level WARN, Vale at suggestion level,
LanguageTool (status ok, 0 matches, run under JDK 17 rather than the JDK 8
false green), paragraph capitalization, snippets, xrefs, structure, links and
missing-code-blocks all clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* "-pl common" made a device build a no-op that reported success
Review caught the command I had just written. AbstractBuildWrapperMojo.execute()
opens with:
if (!project.isExecutionRoot()) {
getLog().info("Skipping execution for non-root project");
return;
}
and BuildAndroidMojo, BuildIosMojo and BuildWin32Mojo all inherit it unchanged.
Under `-pl common` the selected module is not the execution root, so the goal
logs that line and stops. The design is deliberate -- the wrapper re-invokes
Maven on the root pom itself (`request.setPomFile(new File("pom.xml"))`) -- so
naming a module both skips the wrapper and defeats its purpose.
Measured rather than reasoned, against scripts/hellocodenameone:
mvn -pl common ...:buildWin32 -> "Skipping execution for non-root project"
BUILD SUCCESS, nothing built
mvn ...:buildWin32 -> no skip line, proceeds into the reactor
A silent success is the worst failure mode for a getting-started instruction, so
the Introduction now says where to run it and warns about the module form
explicitly.
Working-With-Windows carried the same `-pl common` shape and is fixed with it --
it is where I copied the form from, so leaving it would reintroduce the defect
the next time someone follows the pattern.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* The Windows build wrappers named a platform no profile answers to
Review checked the command this PR documents and found it still builds nothing.
The wrapper passes `codename1.platform` to its nested Maven run, and that
property is what activates the module profile in a generated project's root pom.
The profile is `win`, matching the value the win module itself declares:
<profile><id>win</id>
<activation><property>
<name>codename1.platform</name><value>win</value>
</property></activation>
<modules><module>win</module></modules>
BuildWin32Mojo and BuildWindowsDeviceMojo both passed "windows", which matches no
profile at all. Nothing else in the plugin reads the platform as "windows", so
the value was simply inert: the win module never joined the reactor and the
nested build reported success having produced no Windows binary. Every other
wrapper already agrees with its profile -- android, ios, javascript, linux and
javase all match -- so these two were the only ones out of step.
Verified by A/B against scripts/hellocodenameone, reading the NESTED reactor
rather than the outer one:
before hellocodenameone, -common, -javase <- no win module
after hellocodenameone, -common, -win
The build TARGET is a separate namespace and stays "windows-device"
(Executor.BUILD_TARGET_WINDOWS_NATIVE), which is correct.
SpotBugs over codenameone-maven-plugin regenerated: 0 findings.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* The primary Win32 command had both defects the convenience goal just lost
Fixing BuildWin32Mojo made the sentence beside this snippet false: the goal now
submits a build, while the command the chapter leads with still could not. It
carried both defects at once --
mvn -pl common package -Dcodename1.platform=windows ...
-- `-pl common` builds that module instead of the reactor, so the win module is
never reached, and `windows` matches no profile even when the reactor is whole.
The two mistakes hid each other: with only the module selected, the platform
value had nothing left to activate.
Corrected to what the wrapper actually runs, read off AbstractBuildWrapperMojo:
goal `package`, `codename1.platform=win`, `codename1.buildTarget=windows-device`,
from the project root.
A/B against scripts/hellocodenameone, reading the reactor, which Maven prints
before it compiles:
old no win module in the reactor at all
new hellocodenameone, -common, -win
Both invocations then fail identically on this machine with "invalid target
release: 17", because tools/env.sh pins JDK 8 while the demo targets 17. That is
environmental and equal on both sides, so it does not affect the comparison.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@shai-almog
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Gates for the developer guide defects Asciidoctor cannot see - #5669

Open
shai-almog wants to merge 17 commits into
masterfrom
devguide-guards
Open

Gates for the developer guide defects Asciidoctor cannot see#5669
shai-almog wants to merge 17 commits into
masterfrom
devguide-guards

Conversation

@shai-almog

Copy link
Copy Markdown
Collaborator

Split out of #5664 so the structural repairs there stop waiting on tooling review. Stacked on it, because these checks are written against a guide that #5664 fixes — run them on master and several fail by design.

Four checks. Each was verified to fail when the defect it describes is reintroduced, and to pass again when it is reverted.

CheckCatchesAsciidoctor's behaviour
check-guide-structure.pya document in the tree but not in the book; a chapter that silently becomes a part, a subsection, or a duplicate; an included chapter whose title does not survive into the rendered outputsilent
check-guide-xrefs.pyan internal link that resolves to nothing, and one that renders as a bare [some-id]silent — a reference with link text renders as an ordinary link to nowhere
check-missing-code-blocks.pyprose that promises a listing where a hole followsnot its job
check-guide-links.pylinks checked against the paths the site actually servesnot its job

How the link checker decides

It does not carry a list of good links. It derives what the site serves and follows the rules to what they produce:

  • _redirects sources compiled to patterns, splat and :placeholder included, then followed to their destination rather than accepted for matching. /*.html matches every root-level .html path there is, so a match proves nothing.
  • Hugo routes from front matter — url, slug, aliases — because 1055 of the 1262 content pages override their route with slug, and deriving it from the filename both invents routes and rejects real ones.
  • Draft and future-dated pages excluded, since Hugo defaults buildDrafts and buildFuture to false.
  • Static assets enumerated as files; a directory without an index.html is not a route.
  • /developer-guide/ derived from what scripts/website/build.sh rsyncs. Only /javadoc/ stays exempt, because it is produced from the framework sources and cannot be enumerated here.

Ratchets

check-missing-code-blocks.py records 406 existing holes and check-guide-links.py38 bad links. Both are multisets — one line per occurrence, because a file naming the same bad URL twice has two problems.

Both may only shrink. A fixed entry that is still baselined fails, so the ratchet actually banks its fixes; growing one requires --allow-new, so recording new debt is deliberate rather than a side effect of regenerating.

Where they run

developer-guide-docs.yml runs all four. check-guide-links.py also runs from website-docs.yml, which triggers on docs/website/** — a page renamed or re-slugged there breaks the guide, and putting those paths into the guide workflow would run the whole guide build, maven install and screenshot generation included, for every blog post the daily publisher pushes.

The docs paths filter now covers scripts/developer-guide/** rather than two named files, because a change to a script not in that filter started the workflow while skipping the steps that script governs.

@chatgpt-codex-connector

chatgpt-codex-connectorBot commented Sep 2, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

ReviewStatusCommitReview trigger
📝 Code Review🔄 Running since 2026-09-02T15:38:41.729150Za9c0b73New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:2da41dd8c7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-links.py Outdated
Comment threadscripts/developer-guide/check-guide-structure.py

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:28f950b311

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-links.py

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:21e513112f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-links.py Outdated
@github-actions

Copy link
Copy Markdown
Contributor

Cloudflare Preview

@github-actions

github-actionsBot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Developer Guide build artifacts are available for download from this workflow run:

Developer Guide quality checks:

  • AsciiDoc linter: No issues found (report)
  • Vale: No alerts found (report)
  • Paragraph capitalization: No paragraph capitalization issues (report)
  • LanguageTool: No grammar matches (report)
  • Image references: No unused images detected (report)

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:6d0560cc05

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-structure.py
Comment threadscripts/developer-guide/check-guide-links.py

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:b96d1321a1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-links.py Outdated
Comment threadscripts/developer-guide/check-guide-links.py
@shai-almog
shai-almogforce-pushed the devguide-structure-and-guards branch from 6108cad to e113f04CompareSeptember 2, 2026 11:57

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:d82c0ea525

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-structure.py

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:42189c33d9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-links.py
Comment threadscripts/developer-guide/check-guide-links.py

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:89e588ddc8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-missing-code-blocks.py Outdated
Comment threadscripts/developer-guide/check-guide-xrefs.py Outdated

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:b88150d821

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-xrefs.py
Comment threadscripts/developer-guide/check-guide-structure.py Outdated
Base automatically changed from devguide-structure-and-guards to masterSeptember 2, 2026 13:19
shai-almogand others added 10 commits September 2, 2026 16:20
Four checks, split out of the structural repairs they were written alongside so
each can be reviewed on its own terms. Every one was verified to fail when the
defect it describes is reintroduced, and to pass again when it is reverted.
check-guide-structure.py -- every document is in the book or declared out of it;
no chapter silently becomes a part, a subsection, or a duplicate; and, by
rendering the book and reading its headings back, every included chapter's title
survives into the output. That last check is what would have caught a whole
chapter rendering as subsections of the one before it.
check-guide-xrefs.py -- every internal link resolves, and none renders as a bare
"[some-id]". Asciidoctor reports neither: a reference carrying link text renders
as an ordinary link to nowhere.
check-missing-code-blocks.py -- prose that promises a listing where a hole
follows. 406 exist, from the snippet extraction in bbdc605, recoverable from
bbdc605~1.
check-guide-links.py -- links checked against the paths the site actually
serves, derived from _redirects and the Hugo content tree and followed through
every redirect rule to what it produces, rather than accepted for matching one.
The two ratchets may only shrink; growing one needs --allow-new, so recording
debt is deliberate rather than a side effect of regenerating.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The stacked content change restored the video capture constraints examples, so
their baseline entries went stale and the ratchet refused to pass until they
were removed. 406 to 402.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The outcome check compared counts of normalized headings at any depth, which a
collision satisfies: "Analytics" is a chapter and also a subsection of Commerce,
so the rendered book contains it twice. If the chapter were swallowed, the
subsection kept the count at one and the check -- the central safeguard of this
change -- passed. Measured on the current book: analytics appears twice at any
level and once at chapter level; "getting started" four times at any level and
never at chapter level.
Manifest entries are now counted against chapter-level headings only. Nested
fragments still use the any-level count, because their depth is whatever their
parent gives them.
Second finding: resolves() tried every matching redirect rule and passed if any
chain worked. The host applies the FIRST match and stops, so a link matching an
early rule that leads somewhere deleted would have passed on the strength of a
later rule the reader never reaches. It now returns the first match's result. No
guide link changes verdict today; the point is that the model matches the
behaviour it claims to model.
Also declined, with the reasoning left in the code rather than a review thread:
adding Markdown's three-backtick fence to the literal-block list. The guide
contains none, and validate-guide-snippets.py requires every listing to be
[source,LANG] with a bare include:: inside ---- delimiters, so such a block
would fail that gate before reaching this one.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The pattern required empty brackets, which is right for ifdef and ifndef -- with
content they are the single-line form and guard only that line -- but wrong for
ifeval, which has no single-line form and always carries its expression in the
brackets. So the directive was listed and never recognised.
Latent: the manifest contains no ifeval, and its only conditionals are two
single-line ifdef attribute assignments, which are still correctly treated as
not opening a block. Fixed because a pattern that names three directives and
handles two is a bug in what the code claims, not a missing feature.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review asked for AsciiDoc comment-state tracking so that a URL inside a `//`
line or a `////` block is skipped. Declining, and recording it where the next
reader of this loop will look rather than in a review thread nobody reads.
The guide has 120 files, zero commented-out URLs and zero `////` blocks, so the
tracking would govern nothing that exists. It would also weaken the gate: with
comments skipped, commenting a line out makes its finding disappear and lets the
ratchet shrink, banking a "fix" while the dead link stays in the source waiting
to be uncommented. Deleting the link is the fix.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review asked for fragment validation on same-site URLs. Measuring first: the
guide holds 42 same-site URLs with a fragment, and 38 are /javadoc/ -- the one
tree this script cannot enumerate, because it is generated from the framework
sources at build time. The other four all pointed at
`developer-guide.html#<anchor>`: this book, linking into its own body.
Those are the same defect as the fifteen `/manual/*.html` deep links already
converted in this stack, missed because they use a different path. They send a
reader out to the web to fetch the page they are reading, they do not work in
the PDF, and a renamed section breaks them silently -- the anchor lives in the
rendered book, so no amount of site-path checking can see it.
So the fix is not to validate the fragment, it is to stop writing the link:
all four become `<<anchor,text>>` xrefs, which check-guide-xrefs.py already
resolves against the rendered anchors. All three targets exist today, so this
repairs no live 404 -- it moves four unguarded links under a gate.
The rule closes the class rather than the four instances: any codenameone.com
link whose path is one of this book's own routes and which carries a fragment is
now reported. Verified non-vacuous by restoring one of the four and watching it
fail, then reverting.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both findings describe constructs the guide does not contain, so neither is a
live defect. Both are real holes in the checkers though, and each closes for a
few lines, so they close rather than getting argued with.
Conditional includes (check-guide-structure.py). A manifest entry inside an
ifdef/ifeval was skipped by the duplicate count -- correctly, since the same
chapter under two exclusive branches is one chapter in the output -- and also by
the rendered-title check, since only one branch renders. Skipping both means a
chapter included twice inside a single ACTIVE branch would pass silently. Rather
than teach the checker to distinguish exclusive branches for a construct that
does not exist (measured: zero include:: lines sit inside a conditional anywhere
in the guide), a conditional manifest entry is now refused outright, with a
message saying what to extend if one is ever wanted.
Trailing slashes on file paths (check-guide-links.py). Site paths were compared
with the trailing slash stripped, so a link to `/x.jar/` was validated against
`/x.jar`. The reviewer's premise checks out: _redirects declares the two forms as
separate routes and spells both out where both work, 32 such pairs. Preserving
the slash everywhere would break the many legitimate directory links, so the
narrow case is reported instead -- a last path segment containing a dot, wearing
a trailing slash. Directory routes such as /blog/ and /javadoc/com/codename1/io/
have no dot in the last segment and are untouched.
Both rules verified non-vacuous by introducing an instance and reading the real
exit status rather than a pipeline's: structure and links each exit 1 on the
probe and 0 once it is reverted.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review is right that the rendered-title check has slack: it counts normalized
titles across the whole book, so a nested fragment losing its heading can be
covered by an identical heading elsewhere. Confirmed rather than accepted --
removing the leveloffset from both Maven includes takes "Getting started" from 4
rendered headings to 3, while only one file declares it, so `3 >= 1` and the gate
passed a genuinely swallowed section.
Rather than give nested fragments a rendered identity, which the HTML does not
carry, this checks the cause. The spacing rule already existed but ran only on
the root manifest, and its condition was "a blank line follows". Both were wrong.
Reproduced minimally to find the real rule -- a heading is absorbed only when ALL
of these hold:
* the include is followed immediately by content, and
* neither this include nor the following one carries leveloffset (asciidoctor
brackets the content with :leveloffset: attribute entries, and an attribute
entry closes the paragraph -- the one on the FOLLOWING include lands between
the paragraph and the heading, so it protects just as well), and
* the included file does not end on a blank line, and
* its last line is ordinary paragraph text -- a delimiter, table row, heading,
attribute entry or comment all close the paragraph. That is why
_generated-build-hints.adoc, which ends on "|===", does not eat the
"Versioned builds" heading directly after it.
Against the real book, all four combinations now agree with what asciidoctor
actually renders:
both leveloffset 4 headings renders ok gate passes
only current 4 headings renders ok gate passes
only following 4 headings renders ok gate passes
neither 3 headings SWALLOWED gate fails
An earlier draft exempted only the current include and failed the third row --
a false positive on markup that renders correctly. Every guide include is now
checked, not just the manifest's: measured across all 120 files, this leaves zero
findings, because the five adjacent includes in Maven-Project-Workflow.asciidoc
are protected by leveloffset.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… the rest
Three link findings, and only the first was a live defect -- in the rule I added
one commit earlier.
The trailing-slash rule was unconditional, so it reported "/videos.html/" as
broken even though _redirects declares that exact source and the site serves it.
The rules here all compile slash-insensitively ("^...$/?"), which is right for
matching but discards the distinction the site actually draws, so the raw sources
are now collected alongside them and a slashed file path is reported only when
_redirects does not spell it out. Probed both ways: "/videos.html/" passes,
"/files/CodenameOneBuildClient.jar/" fails.
Hugo route overrides. hugo.toml sets neither [permalinks] nor uglyURLs today, so
deriving a route as "section path + slug" is currently correct; if either
appears, every route underneath moves and this would go on accepting links to
paths Hugo no longer publishes. Modelling a configuration that is not there would
be guesswork, and reading the built public/ tree makes a local run depend on a
tree that may be stale or absent. So it now notices instead: either key present
aborts the check with what to do about it. Probed with each key in turn.
Protocol-relative links and fragments on ordinary same-site pages are declined,
with the measurements recorded in the code rather than in a review thread nobody
reads. The guide has no `link://` macro at all, and its one bare "//host/path" is
a JavaScript string inside a source block, so widening URL_RE to match "//" would
start reporting code as a broken link. And of the 42 same-site URLs carrying a
fragment, 38 are /javadoc/ -- generated at build time, exempt for that reason --
while the other four pointed into this book and are now xrefs, which
check-guide-xrefs.py resolves against the rendered anchors. Nothing is left that
this script could check without building the Hugo site.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ting
Two more holes in the guards. Neither has an instance in the guide today, and
both close cheaply enough that arguing about them would cost more.
check-guide-xrefs.py rendered the book once, with the default backend, so
anything inside ifdef::backend-pdf[] was dropped before a single reference in it
could be examined -- while the workflow publishes an asciidoctor-pdf build from
the same source. Native-Themes.asciidoc already has two such branches. It now
renders twice, the second time with -a backend-pdf, which selects exactly the
content the PDF build includes without needing asciidoctor-pdf here. The two runs
are checked SEPARATELY rather than pooled: an anchor that exists only in the HTML
branch must not satisfy a reference made in the PDF branch. Findings name the
branch they came from.
A/B with a dangling xref planted inside the backend-pdf branch of
Native-Themes.asciidoc:
old "Cross-references OK: 1741 anchors" -- exit 0
new "Native-Themes.asciidoc:506: <<definitely-not-a-real-anchor>> ...
(1 reference(s), pdf render)" -- exit 1
check-missing-code-blocks.py accepted any `[attribute]` line as the start of the
promised block, so an admonition standing where a listing used to be hid the
hole. An admonition is prose and can never be the listing a sentence promised, so
the five names are excluded. Excluded by name rather than by whitelisting the
kinds that ARE code: measured, the bracket lines legitimately answering a
promising sentence already span [source] (582), [cols=...] and [options=...] (56),
[listing] (3), [quote] (2) and an anchored image, and a whitelist would report the
next kind nobody anticipated.
A/B with each block planted after a promising sentence, using the real signature
of a removed listing -- the two blank lines it leaves behind:
[NOTE] old: none new new: reported as a hole
[source,java] old: none new new: none new
The baseline is unchanged at 402, because the guide currently has no admonition
sitting in a listing's place.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…separator
Two findings, and this time the first one is live.
The rendered book contains exactly three relative hrefs, and all three are
broken: css.asciidoc carried link:Images[], link:Fonts[] and
link:Supported-Properties#text-decoration[] over from the wiki this guide
replaced, naming pages that were never carried across with them. The guide
renders as a single page, so a relative href resolves against wherever that page
is served and reaches nothing that ships with it -- the reader gets a 404 while
every gate reported success. All three targets are sections in that same file:
[[Images]] already existed, [[Fonts]] is added to match it, and
[[text-decoration]] was already there and already used by a sibling reference.
check-guide-xrefs.py now reports any relative href, which after this change has
zero instances. Absolute URLs stay with check-guide-links.py, and root-relative
paths and fragments are excluded. A/B with one link restored: old printed
"Cross-references OK" and exited 0, new reports it and exits 1.
The second is a wrong claim in my own comment. check-guide-structure.py returned
early when an include was followed by ifdef/ifndef/ifeval/endif, on the reasoning
that "a preprocessor directive is not content and cannot absorb a paragraph".
True, and irrelevant: asciidoctor REMOVES the directive during preprocessing, so
it does not separate anything either. Measured -- with `ifdef::backend-html5[]`
between an include ending in prose and a following heading, the render contains
zero heading tags and the paragraph reads "...preferred tools. === A parent-local
heading". The rule now scans past directive lines to the first line that survives
preprocessing. A/B on that construct: old passed it, new reports it.
Note the rendered-title check cannot cover this case either, because a
parent-local heading is not the first heading of any included file.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:f9e50cb8cc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-links.py
Comment threadscripts/developer-guide/check-guide-xrefs.py
… has a limit
The first finding was live: the link ratchet held a working link.
docs/website/functions/[[path]].js runs after context.next() has already 404ed
and then redirects anything under /files/ or /demos/ to
download.codenameone.com. Those paths are served, so the model was missing a
whole class of route and had banked a real one -- /files/iOS_UI-Kit.psd -- as
broken. The destination is off-site, which puts it in the same bucket as every
other off-site redirect: reachable, not verifiable from this repository. The
rules are appended AFTER the _redirects rules because the function is a fallback
and the first matching rule wins, mirroring how the host evaluates. The baseline
shrinks 37 -> 36, and a new /files/ link is now accepted rather than failing CI.
The second is a real limit of the PDF surrogate, and one the command line cannot
remove. Setting backend-pdf makes ifndef::backend-pdf[] content disappear and
ifdef::backend-pdf[] content appear -- verified, and that is the only form this
guide uses. It does not undefine backend-html5: the HTML converter sets that
itself, after command-line attributes are applied, so even `-a backend-html5!`
leaves it defined. Measured both ways. Content guarded on the HTML backend would
therefore survive into the surrogate render and could satisfy a PDF-only
reference that the real asciidoctor-pdf build leaves dangling.
The guide has no such conditional, so rather than model a construct that is not
there -- or drive asciidoctor-pdf and try to read anchors back out of a PDF --
the checker refuses one if it appears, naming the alternative the rest of the
guide already uses. Probed: planting an ifdef::backend-html5[] block aborts the
check with that message.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:6a8963d02a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-structure.py Outdated
…side one
Both scanners matched a delimiter of exactly four characters, so a block opened
with ----- was invisible to them and its contents were scanned as live markup.
The guide has 20 such lines -- eight in Desktop-Integration, six in
Working-With-iOS, six in appendix_goal_generate_archetype -- every one an
ordinary [source] block wrapping an include::. validate-guide-snippets.py already
accepted -{4,}; these two had drifted from it.
Nothing is misread today, because those includes name .java, .xml and .properties
files and the reachability walk skips non-asciidoc targets. The failure mode is
still the interesting one though: it is a FALSE POSITIVE generator, not a missed
defect. A/B with each construct planted inside a five-dash block:
a displayed include::Working-with-UWP.asciidoc[]
old reported a swallowed-heading error against markup that only appears
on the page as text
new clean
a displayed "The wrapper looks like this:" followed by blank lines
old reported it as a missing code block
new clean
Both now track the delimiter that opened the block and close on one of the same
character AND length, which is what asciidoctor does -- so a four-dash line
inside a five-dash block is content rather than the close. The set stays limited
to literal blocks: an example (====) or sidebar (****) block contains live
markup, so headings and includes inside one are real and must keep counting.
Baselines unchanged: 119 included documents, 402 holes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:57212b7f43

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-structure.py
Comment threadscripts/developer-guide/check-guide-xrefs.py
shai-almogand others added 2 commits September 2, 2026 18:21
* Guide figures render with a bundled font instead of the host's
None of the 24 generated figures could be reproduced outside CI. Running the
generator on a Mac reproduces 0 of 24 byte-for-byte, with 3-20% of pixels
differing. Two consecutive local runs are byte-identical, so the generator is
deterministic on one host; the variable is the font.
`PreAdvancedThemingScreenshots` styled with `Font.createSystemFont`, which
resolves through `JavaSEPort.fontFaceSystem` -- "Arial" on macOS and Linux
alike. Arial exists on a developer's Mac and not on a stock CI runner, so AWT
silently substitutes and every glyph changes. The differences are exactly that:
for `flow-layout.png` every differing pixel sits in y 10-130, the title and
label rows, while the colored blocks below match to the pixel.
So the byte-exact gate has only ever been green because CI both generates and
verifies. A developer regenerating locally could not match it, and at the scale
the guide's remaining ~260 app screenshots would need, that is untenable.
The figures now load `native:MainRegular`, which `JavaSEPort.loadTrueTypeFont`
reads from `/com/codename1/impl/javase/Roboto-Medium.ttf` on the classpath
rather than from an installed-font lookup. This is also what the project's font
rule requires everywhere: never `createSystemFont`, always the `native:` scheme.
A null return refuses loudly rather than falling back to a host font, because a
silent fallback would restore the exact non-determinism this removes.
The committed baselines are regenerated from a Mac. CI byte-compares them on
Linux, so the check either passes -- proving host independence rather than
asserting it -- or fails and says so immediately.
Composition, dimensions and colours are unchanged; only the typeface moves.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Pin the font on every component, not just the ones that get block colours
CI reported one stale figure out of 24: `guibuilder-2-insets-3.png`, the only
one containing a `TextField`. The other 23 matched Linux byte for byte, so the
bundled-font change worked -- it just did not reach far enough.
`applyBlockStyleToContent` styled `Label` and `Button`. Everything else kept the
theme's default font, which resolves through the host, so the text field and its
hint were still host-dependent. Enumerating the types that carry text would have
left the next one added broken the same way, so the walk now pins the face on
every component it visits, plus the hint label, which is painted by a `Label`
that is not in the component tree and so is never reached by the walk.
The first attempt used `BLOCK_FONT` for this and regressed the figure: at 29px
against the theme default's 13px the field grew and squeezed "Submit" down to a
clipped sliver. `FIELD_FONT` is sized to reproduce the original height, so the
composition is unchanged and only the typeface moves.
Verified locally by rendering twice, once with `JavaSEPort.setFontFaces` pointed
at a family that does not exist -- which is what a machine without Arial looks
like to the port. All 24 come out byte-identical, so nothing in these figures
reads an installed font any more.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Publish what the runner actually rendered when a screenshot fails
"Committed screenshot is stale: <name>" names the file and nothing else, so
there is no way to tell a real regression from an environment difference
without adding a debugging round trip to CI. The generated directory is now
uploaded as an artifact when the step fails.
Needed immediately: one figure still differs between a Mac and the runner after
the font fix, and host fonts, JDK version and working directory have each been
ruled out locally.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Pin MigLayout's platform, and stop demanding byte equality of a glyph edge
The artifact step paid for itself immediately. Downloading what the runner
rendered showed two figures differing, not the one the gate reported -- it exits
on the first mismatch -- and the two had nothing in common.
`mig-layout.png` differed by 12.8% of its pixels, a real layout shift.
MigLayout takes its default gaps from `PlatformDefaults`, which reads
`System.getProperty("os.name")` and answers MAC_OSX, GNOME or WINDOWS_XP, each
with different spacing. The figure was rendering with macOS gaps on a Mac and
GNOME gaps on the runner. Pinning the platform fixes it: with the pin, 23 of the
24 figures now match the runner's own output byte for byte.
`layered-layout.png` differed by 173 pixels, 0.113%, inside a 25x25 box. That
one is not fixable. Measured against the runner's bytes, the material glyph
lands at exactly the same size and the same origin -- a 55x49 bounding box --
and differs only in antialiased edge coverage, 946 fully-white pixels against
916. Java2D rasterizes the same glyph, from the same bundled font, at the same
size, slightly differently on the two platforms. Demanding byte equality there
would mean deleting legitimate content from the figure or carrying a
permanently red check.
So the comparison moves from `cmp -s` to a comparer that still requires byte
equality by default and accepts a bounded difference only where a figure carries
a `.tolerance` sidecar explaining itself, in the same key=value shape the CN1SS
suites already use. The area bound does the work: a per-pixel delta that large is
meaningless on its own, since a glyph edge flips between white and the block
behind it, but a regression that changed the icon would move far more than 0.3%
of the image.
Verified by running the comparer with the runner's own output against the
committed figures -- which is exactly what CI will do -- and by four probes: an
untoleranced figure that differs fails, the toleranced figure fails when changed
beyond its budget, a missing figure fails the count, and a clean run passes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Guard the iOS font branch, and stop the comparator gating itself out
Two review findings on this PR.
The first says `native:MainRegular` does not give host-independent output,
because `loadTrueTypeFont` resolves native fonts to the first installed SF or
Helvetica family before reaching the bundled Roboto. The branch is real, but it
is reached only when `isIOS` is set, which `loadSkinFile` does for a skin whose
systemFontFamily contains "helvetica" -- and this generator never loads a skin.
The measurement agrees: figures rendered on a Mac match the Linux runner byte
for byte, which could not happen if one side were resolving Helvetica Neue and
the other Roboto.
So the conclusion does not hold today, but the risk is real for tomorrow: a
change that loads a skin here would put host fonts back into the output with no
other symptom. The generator now refuses to run under an iOS platform, and says
why, rather than leaving that to a comment nobody reads.
The second finding is straightforwardly right and is the more serious of the
two. `on.pull_request.paths` triggers on `scripts/developer-guide/**`, but the
`Determine changed components` filter named only two scripts, so a pull request
touching any other script here started the workflow with `docs` false and
skipped the steps that script governs. A change to compare-screenshots.py could
have merged without the screenshot check ever running it -- a gate that skips
itself. The filter now covers the whole directory, which also removes the
two-copies-drift the surrounding comment already warns about.
Verified the guard changes no output: all 24 figures are unchanged, and the
comparer still passes against the runner's own bytes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Count every changed pixel toward the area budget, not just the loud ones
The comparator incremented its mismatch counter only for pixels whose channel
delta EXCEEDED maxChannelDelta, which is what the CN1SS comparator does. With a
sidecar written the way this one was -- a large delta bound paired with a small
area bound -- that leaves an unbounded hole, and review gave the exploit in the
figures' own palette: recolouring the green #06a806 to #a608a6 moves every
channel by exactly 160, so with maxChannelDelta=160 not one pixel is counted and
a dramatically different image reports zero mismatches.
Reproduced it before fixing: the recolour changes 4191 pixels, 2.73% of the
image, at a worst delta of exactly 160 -- and passed.
The two bounds are now independent. maxMismatchPercent limits how much of the
image may change at all, counting every differing pixel; maxChannelDelta caps how
far any single pixel may move. The measured legitimate noise -- 173 pixels,
0.113%, worst delta 141 -- still passes, the recolour now fails on area, and a
five-pixel solid overwrite fails on delta.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Correct the sidecar's rationale to match how the bounds now work
The previous commit changed the comparator so every differing pixel counts
toward the area budget and the channel delta is a separate ceiling. The sidecar
still explained the old behaviour -- that the area bound did the work and the
delta was meaningless on its own -- which is now wrong in a file whose whole
purpose is to justify the numbers beside it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Compare figures in RGBA, because they have an alpha channel
The comparator converted both images to RGB before counting differences. The
committed figures are genuine RGBA PNGs -- four channels, currently opaque
everywhere -- so that dropped a real channel, and any change confined to alpha
was invisible to the tolerance path. A regression that turned the whole figure
transparent while leaving every colour channel intact reported zero changed
pixels and passed.
Byte equality, which every figure without a sidecar is still held to, always
caught this. Only a figure carrying a sidecar could reach the weakened path, so
today the exposure was one image -- but that image is exactly the one whose
comparison is relaxed.
Verified: making layered-layout.png fully transparent with its RGB channels
untouched now reports 100% of pixels changed, the real runner output still
passes, and the recolour and untoleranced-difference probes still fail as they
did.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…page
Two findings, one of them a false positive I introduced last round.
Comment blocks. Asciidoctor drops a //// block entirely, so an include inside
one does not put its target in the book and prose inside one promises the reader
nothing -- but both scanners read straight through. //// joins the fence set in
each. A/B with each construct planted inside a comment block: a displayed
include:: made the structure check fail, and a promising sentence made the
missing-block check fail; both are clean now.
Note this is deliberately NOT symmetric with check-guide-links.py, which still
reads URLs inside comments. The reasons differ and the code says so: a dead link
commented out is still dead debt in the source, and letting the ratchet shrink
for it would make "comment the line out" a way to silence that gate. Reachability
is the opposite -- calling a commented-out include reachable states something
about the book that is simply untrue.
Packaged assets. The relative-href rule added last round was too broad: the HTML
packaging step copies every subdirectory of docs/developer-guide next to
developer-guide.html, so link:img/example.png[] resolves for a reader who opens
the zip, and the gate would have rejected it. A relative href that names an
existing file under the guide directory is now allowed. sketch/ is not, because
that is the one directory the packaging loop skips.
Probed all four ways, since an exemption is only worth having if it still
rejects:
link:img/flow-layout.png[] exit 0 (real, packaged)
link:img/does-not-exist.png[] exit 1
link:sketch/whatever.svg[] exit 1 (not packaged)
link:Supported-Properties[] exit 1 (the wiki-era shape)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:a6bd5197dc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-xrefs.py Outdated
Comment threadscripts/developer-guide/check-guide-xrefs.py
shai-almogand others added 2 commits September 2, 2026 18:35
Both findings are defects in the two rules added in the previous commit.
packaged_asset() asked whether the file exists here, which is not the question. A
link only works if the published output carries it, and the two outputs filter
differently:
* the HTML archive copies only the SUBDIRECTORIES of docs/developer-guide next
to developer-guide.html and skips sketch, so a root-level file is never in the
zip however much it exists in the tree;
* the website rsync excludes sketch/, *.asciidoc and *.adoc, so a source file
nested inside a packaged directory still does not reach the site.
So link:Introduction.asciidoc[] was being exempted and readers would follow it to
nothing. Both filters are now reproduced. The permissive direction is the
expensive one here -- it suppresses a finding for a link nobody can follow --
so all four cases are probed:
img/flow-layout.png exit 0 in both outputs
Introduction.asciidoc exit 1 in neither
img/does-not-exist.png exit 1
sketch/foo.svg exit 1 excluded by both
The backend-conditional rejection scanned raw lines, so a source block DISPLAYING
`ifdef::backend-html5[]` as example text aborted the whole gate. A displayed
directive takes no part in preprocessing. It now tracks literal and comment block
delimiters the way the other guide scanners do. A/B: displayed inside a source
block went 1 -> 0, while a genuinely active conditional still exits 1.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…18 statistics (#5671)
* The Introduction taught a dead IDE menu, an iTunes walkthrough and 2018 statistics
Six corrections to the chapter a new developer reads first:
* The JavaScript target was described twice and contradicted itself: the
overview said TeaVM did the translation, the port section said ParparVM with
TeaVM as a fallback. The port section is right -- `javascript.port` is
declared with `parparvm` as its default and `teavm` documented as "the
original builder as a compatibility fallback".
* `http://teavm.org:[TeaVM-based builder]` -- the stray colon made the macro a
bare host, and the site serves TLS. Removed from the link ratchet, which now
stands at 37.
* Bitcode was cited as something ParparVM absorbed without modification. Apple
has since withdrawn bitcode, which makes the point better than the arrival
did, so the sentence now names both moves.
* The device-fragmentation section rested on two 2018 Android share numbers,
followed by a sentence conceding they would be stale on arrival. The
structural claim survives without them.
* The iOS developer fee was dated with "for 10 years at the time of this
writing".
* The UDID instructions walked the reader through iTunes, with a screenshot of
iTunes on iOS 9.3.5 that also exposed a real device serial and UDID. iTunes
has not existed on macOS since Catalina, and the signing chapter already gives
the current answer, so this now points there.
* The device build was "a right click away" via the IDE plugin, illustrated by a
menu still offering Blackberry, J2ME and Windows Phone builds. Replaced with
the `cn1:buildAndroid` and `cn1:buildIos` goals, taken from the mojo names.
Both screenshots are deleted, so find_unused_images.py stays satisfied.
Verified: asciidoctor at --failure-level WARN, Vale at suggestion level,
LanguageTool (status ok, 0 matches, run under JDK 17 rather than the JDK 8
false green), paragraph capitalization, snippets, xrefs, structure, links and
missing-code-blocks all clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* "-pl common" made a device build a no-op that reported success
Review caught the command I had just written. AbstractBuildWrapperMojo.execute()
opens with:
if (!project.isExecutionRoot()) {
getLog().info("Skipping execution for non-root project");
return;
}
and BuildAndroidMojo, BuildIosMojo and BuildWin32Mojo all inherit it unchanged.
Under `-pl common` the selected module is not the execution root, so the goal
logs that line and stops. The design is deliberate -- the wrapper re-invokes
Maven on the root pom itself (`request.setPomFile(new File("pom.xml"))`) -- so
naming a module both skips the wrapper and defeats its purpose.
Measured rather than reasoned, against scripts/hellocodenameone:
mvn -pl common ...:buildWin32 -> "Skipping execution for non-root project"
BUILD SUCCESS, nothing built
mvn ...:buildWin32 -> no skip line, proceeds into the reactor
A silent success is the worst failure mode for a getting-started instruction, so
the Introduction now says where to run it and warns about the module form
explicitly.
Working-With-Windows carried the same `-pl common` shape and is fixed with it --
it is where I copied the form from, so leaving it would reintroduce the defect
the next time someone follows the pattern.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* The Windows build wrappers named a platform no profile answers to
Review checked the command this PR documents and found it still builds nothing.
The wrapper passes `codename1.platform` to its nested Maven run, and that
property is what activates the module profile in a generated project's root pom.
The profile is `win`, matching the value the win module itself declares:
<profile><id>win</id>
<activation><property>
<name>codename1.platform</name><value>win</value>
</property></activation>
<modules><module>win</module></modules>
BuildWin32Mojo and BuildWindowsDeviceMojo both passed "windows", which matches no
profile at all. Nothing else in the plugin reads the platform as "windows", so
the value was simply inert: the win module never joined the reactor and the
nested build reported success having produced no Windows binary. Every other
wrapper already agrees with its profile -- android, ios, javascript, linux and
javase all match -- so these two were the only ones out of step.
Verified by A/B against scripts/hellocodenameone, reading the NESTED reactor
rather than the outer one:
before hellocodenameone, -common, -javase <- no win module
after hellocodenameone, -common, -win
The build TARGET is a separate namespace and stays "windows-device"
(Executor.BUILD_TARGET_WINDOWS_NATIVE), which is correct.
SpotBugs over codenameone-maven-plugin regenerated: 0 findings.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* The primary Win32 command had both defects the convenience goal just lost
Fixing BuildWin32Mojo made the sentence beside this snippet false: the goal now
submits a build, while the command the chapter leads with still could not. It
carried both defects at once --
mvn -pl common package -Dcodename1.platform=windows ...
-- `-pl common` builds that module instead of the reactor, so the win module is
never reached, and `windows` matches no profile even when the reactor is whole.
The two mistakes hid each other: with only the module selected, the platform
value had nothing left to activate.
Corrected to what the wrapper actually runs, read off AbstractBuildWrapperMojo:
goal `package`, `codename1.platform=win`, `codename1.buildTarget=windows-device`,
from the project root.
A/B against scripts/hellocodenameone, reading the reactor, which Maven prints
before it compiles:
old no win module in the reactor at all
new hellocodenameone, -common, -win
Both invocations then fail identically on this machine with "invalid target
release: 17", because tools/env.sh pins JDK 8 while the demo targets 17. That is
environmental and equal on both sides, so it does not affect the comparison.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@shai-almog
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Gates for the developer guide defects Asciidoctor cannot see - #5669

Open
shai-almog wants to merge 17 commits into
masterfrom
devguide-guards
Open

Gates for the developer guide defects Asciidoctor cannot see#5669
shai-almog wants to merge 17 commits into
masterfrom
devguide-guards

Conversation

@shai-almog

Copy link
Copy Markdown
Collaborator

Split out of #5664 so the structural repairs there stop waiting on tooling review. Stacked on it, because these checks are written against a guide that #5664 fixes — run them on master and several fail by design.

Four checks. Each was verified to fail when the defect it describes is reintroduced, and to pass again when it is reverted.

CheckCatchesAsciidoctor's behaviour
check-guide-structure.pya document in the tree but not in the book; a chapter that silently becomes a part, a subsection, or a duplicate; an included chapter whose title does not survive into the rendered outputsilent
check-guide-xrefs.pyan internal link that resolves to nothing, and one that renders as a bare [some-id]silent — a reference with link text renders as an ordinary link to nowhere
check-missing-code-blocks.pyprose that promises a listing where a hole followsnot its job
check-guide-links.pylinks checked against the paths the site actually servesnot its job

How the link checker decides

It does not carry a list of good links. It derives what the site serves and follows the rules to what they produce:

  • _redirects sources compiled to patterns, splat and :placeholder included, then followed to their destination rather than accepted for matching. /*.html matches every root-level .html path there is, so a match proves nothing.
  • Hugo routes from front matter — url, slug, aliases — because 1055 of the 1262 content pages override their route with slug, and deriving it from the filename both invents routes and rejects real ones.
  • Draft and future-dated pages excluded, since Hugo defaults buildDrafts and buildFuture to false.
  • Static assets enumerated as files; a directory without an index.html is not a route.
  • /developer-guide/ derived from what scripts/website/build.sh rsyncs. Only /javadoc/ stays exempt, because it is produced from the framework sources and cannot be enumerated here.

Ratchets

check-missing-code-blocks.py records 406 existing holes and check-guide-links.py38 bad links. Both are multisets — one line per occurrence, because a file naming the same bad URL twice has two problems.

Both may only shrink. A fixed entry that is still baselined fails, so the ratchet actually banks its fixes; growing one requires --allow-new, so recording new debt is deliberate rather than a side effect of regenerating.

Where they run

developer-guide-docs.yml runs all four. check-guide-links.py also runs from website-docs.yml, which triggers on docs/website/** — a page renamed or re-slugged there breaks the guide, and putting those paths into the guide workflow would run the whole guide build, maven install and screenshot generation included, for every blog post the daily publisher pushes.

The docs paths filter now covers scripts/developer-guide/** rather than two named files, because a change to a script not in that filter started the workflow while skipping the steps that script governs.

@chatgpt-codex-connector

chatgpt-codex-connectorBot commented Sep 2, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

ReviewStatusCommitReview trigger
📝 Code Review🔄 Running since 2026-09-02T15:38:41.729150Za9c0b73New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:2da41dd8c7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-links.py Outdated
Comment threadscripts/developer-guide/check-guide-structure.py

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:28f950b311

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-links.py

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:21e513112f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-links.py Outdated
@github-actions

Copy link
Copy Markdown
Contributor

Cloudflare Preview

@github-actions

github-actionsBot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Developer Guide build artifacts are available for download from this workflow run:

Developer Guide quality checks:

  • AsciiDoc linter: No issues found (report)
  • Vale: No alerts found (report)
  • Paragraph capitalization: No paragraph capitalization issues (report)
  • LanguageTool: No grammar matches (report)
  • Image references: No unused images detected (report)

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:6d0560cc05

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-structure.py
Comment threadscripts/developer-guide/check-guide-links.py

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:b96d1321a1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-links.py Outdated
Comment threadscripts/developer-guide/check-guide-links.py
@shai-almog
shai-almogforce-pushed the devguide-structure-and-guards branch from 6108cad to e113f04CompareSeptember 2, 2026 11:57

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:d82c0ea525

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-structure.py

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:42189c33d9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-links.py
Comment threadscripts/developer-guide/check-guide-links.py

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:89e588ddc8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-missing-code-blocks.py Outdated
Comment threadscripts/developer-guide/check-guide-xrefs.py Outdated

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:b88150d821

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-xrefs.py
Comment threadscripts/developer-guide/check-guide-structure.py Outdated
Base automatically changed from devguide-structure-and-guards to masterSeptember 2, 2026 13:19
shai-almogand others added 10 commits September 2, 2026 16:20
Four checks, split out of the structural repairs they were written alongside so
each can be reviewed on its own terms. Every one was verified to fail when the
defect it describes is reintroduced, and to pass again when it is reverted.
check-guide-structure.py -- every document is in the book or declared out of it;
no chapter silently becomes a part, a subsection, or a duplicate; and, by
rendering the book and reading its headings back, every included chapter's title
survives into the output. That last check is what would have caught a whole
chapter rendering as subsections of the one before it.
check-guide-xrefs.py -- every internal link resolves, and none renders as a bare
"[some-id]". Asciidoctor reports neither: a reference carrying link text renders
as an ordinary link to nowhere.
check-missing-code-blocks.py -- prose that promises a listing where a hole
follows. 406 exist, from the snippet extraction in bbdc605, recoverable from
bbdc605~1.
check-guide-links.py -- links checked against the paths the site actually
serves, derived from _redirects and the Hugo content tree and followed through
every redirect rule to what it produces, rather than accepted for matching one.
The two ratchets may only shrink; growing one needs --allow-new, so recording
debt is deliberate rather than a side effect of regenerating.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The stacked content change restored the video capture constraints examples, so
their baseline entries went stale and the ratchet refused to pass until they
were removed. 406 to 402.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The outcome check compared counts of normalized headings at any depth, which a
collision satisfies: "Analytics" is a chapter and also a subsection of Commerce,
so the rendered book contains it twice. If the chapter were swallowed, the
subsection kept the count at one and the check -- the central safeguard of this
change -- passed. Measured on the current book: analytics appears twice at any
level and once at chapter level; "getting started" four times at any level and
never at chapter level.
Manifest entries are now counted against chapter-level headings only. Nested
fragments still use the any-level count, because their depth is whatever their
parent gives them.
Second finding: resolves() tried every matching redirect rule and passed if any
chain worked. The host applies the FIRST match and stops, so a link matching an
early rule that leads somewhere deleted would have passed on the strength of a
later rule the reader never reaches. It now returns the first match's result. No
guide link changes verdict today; the point is that the model matches the
behaviour it claims to model.
Also declined, with the reasoning left in the code rather than a review thread:
adding Markdown's three-backtick fence to the literal-block list. The guide
contains none, and validate-guide-snippets.py requires every listing to be
[source,LANG] with a bare include:: inside ---- delimiters, so such a block
would fail that gate before reaching this one.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The pattern required empty brackets, which is right for ifdef and ifndef -- with
content they are the single-line form and guard only that line -- but wrong for
ifeval, which has no single-line form and always carries its expression in the
brackets. So the directive was listed and never recognised.
Latent: the manifest contains no ifeval, and its only conditionals are two
single-line ifdef attribute assignments, which are still correctly treated as
not opening a block. Fixed because a pattern that names three directives and
handles two is a bug in what the code claims, not a missing feature.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review asked for AsciiDoc comment-state tracking so that a URL inside a `//`
line or a `////` block is skipped. Declining, and recording it where the next
reader of this loop will look rather than in a review thread nobody reads.
The guide has 120 files, zero commented-out URLs and zero `////` blocks, so the
tracking would govern nothing that exists. It would also weaken the gate: with
comments skipped, commenting a line out makes its finding disappear and lets the
ratchet shrink, banking a "fix" while the dead link stays in the source waiting
to be uncommented. Deleting the link is the fix.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review asked for fragment validation on same-site URLs. Measuring first: the
guide holds 42 same-site URLs with a fragment, and 38 are /javadoc/ -- the one
tree this script cannot enumerate, because it is generated from the framework
sources at build time. The other four all pointed at
`developer-guide.html#<anchor>`: this book, linking into its own body.
Those are the same defect as the fifteen `/manual/*.html` deep links already
converted in this stack, missed because they use a different path. They send a
reader out to the web to fetch the page they are reading, they do not work in
the PDF, and a renamed section breaks them silently -- the anchor lives in the
rendered book, so no amount of site-path checking can see it.
So the fix is not to validate the fragment, it is to stop writing the link:
all four become `<<anchor,text>>` xrefs, which check-guide-xrefs.py already
resolves against the rendered anchors. All three targets exist today, so this
repairs no live 404 -- it moves four unguarded links under a gate.
The rule closes the class rather than the four instances: any codenameone.com
link whose path is one of this book's own routes and which carries a fragment is
now reported. Verified non-vacuous by restoring one of the four and watching it
fail, then reverting.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both findings describe constructs the guide does not contain, so neither is a
live defect. Both are real holes in the checkers though, and each closes for a
few lines, so they close rather than getting argued with.
Conditional includes (check-guide-structure.py). A manifest entry inside an
ifdef/ifeval was skipped by the duplicate count -- correctly, since the same
chapter under two exclusive branches is one chapter in the output -- and also by
the rendered-title check, since only one branch renders. Skipping both means a
chapter included twice inside a single ACTIVE branch would pass silently. Rather
than teach the checker to distinguish exclusive branches for a construct that
does not exist (measured: zero include:: lines sit inside a conditional anywhere
in the guide), a conditional manifest entry is now refused outright, with a
message saying what to extend if one is ever wanted.
Trailing slashes on file paths (check-guide-links.py). Site paths were compared
with the trailing slash stripped, so a link to `/x.jar/` was validated against
`/x.jar`. The reviewer's premise checks out: _redirects declares the two forms as
separate routes and spells both out where both work, 32 such pairs. Preserving
the slash everywhere would break the many legitimate directory links, so the
narrow case is reported instead -- a last path segment containing a dot, wearing
a trailing slash. Directory routes such as /blog/ and /javadoc/com/codename1/io/
have no dot in the last segment and are untouched.
Both rules verified non-vacuous by introducing an instance and reading the real
exit status rather than a pipeline's: structure and links each exit 1 on the
probe and 0 once it is reverted.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review is right that the rendered-title check has slack: it counts normalized
titles across the whole book, so a nested fragment losing its heading can be
covered by an identical heading elsewhere. Confirmed rather than accepted --
removing the leveloffset from both Maven includes takes "Getting started" from 4
rendered headings to 3, while only one file declares it, so `3 >= 1` and the gate
passed a genuinely swallowed section.
Rather than give nested fragments a rendered identity, which the HTML does not
carry, this checks the cause. The spacing rule already existed but ran only on
the root manifest, and its condition was "a blank line follows". Both were wrong.
Reproduced minimally to find the real rule -- a heading is absorbed only when ALL
of these hold:
* the include is followed immediately by content, and
* neither this include nor the following one carries leveloffset (asciidoctor
brackets the content with :leveloffset: attribute entries, and an attribute
entry closes the paragraph -- the one on the FOLLOWING include lands between
the paragraph and the heading, so it protects just as well), and
* the included file does not end on a blank line, and
* its last line is ordinary paragraph text -- a delimiter, table row, heading,
attribute entry or comment all close the paragraph. That is why
_generated-build-hints.adoc, which ends on "|===", does not eat the
"Versioned builds" heading directly after it.
Against the real book, all four combinations now agree with what asciidoctor
actually renders:
both leveloffset 4 headings renders ok gate passes
only current 4 headings renders ok gate passes
only following 4 headings renders ok gate passes
neither 3 headings SWALLOWED gate fails
An earlier draft exempted only the current include and failed the third row --
a false positive on markup that renders correctly. Every guide include is now
checked, not just the manifest's: measured across all 120 files, this leaves zero
findings, because the five adjacent includes in Maven-Project-Workflow.asciidoc
are protected by leveloffset.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… the rest
Three link findings, and only the first was a live defect -- in the rule I added
one commit earlier.
The trailing-slash rule was unconditional, so it reported "/videos.html/" as
broken even though _redirects declares that exact source and the site serves it.
The rules here all compile slash-insensitively ("^...$/?"), which is right for
matching but discards the distinction the site actually draws, so the raw sources
are now collected alongside them and a slashed file path is reported only when
_redirects does not spell it out. Probed both ways: "/videos.html/" passes,
"/files/CodenameOneBuildClient.jar/" fails.
Hugo route overrides. hugo.toml sets neither [permalinks] nor uglyURLs today, so
deriving a route as "section path + slug" is currently correct; if either
appears, every route underneath moves and this would go on accepting links to
paths Hugo no longer publishes. Modelling a configuration that is not there would
be guesswork, and reading the built public/ tree makes a local run depend on a
tree that may be stale or absent. So it now notices instead: either key present
aborts the check with what to do about it. Probed with each key in turn.
Protocol-relative links and fragments on ordinary same-site pages are declined,
with the measurements recorded in the code rather than in a review thread nobody
reads. The guide has no `link://` macro at all, and its one bare "//host/path" is
a JavaScript string inside a source block, so widening URL_RE to match "//" would
start reporting code as a broken link. And of the 42 same-site URLs carrying a
fragment, 38 are /javadoc/ -- generated at build time, exempt for that reason --
while the other four pointed into this book and are now xrefs, which
check-guide-xrefs.py resolves against the rendered anchors. Nothing is left that
this script could check without building the Hugo site.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ting
Two more holes in the guards. Neither has an instance in the guide today, and
both close cheaply enough that arguing about them would cost more.
check-guide-xrefs.py rendered the book once, with the default backend, so
anything inside ifdef::backend-pdf[] was dropped before a single reference in it
could be examined -- while the workflow publishes an asciidoctor-pdf build from
the same source. Native-Themes.asciidoc already has two such branches. It now
renders twice, the second time with -a backend-pdf, which selects exactly the
content the PDF build includes without needing asciidoctor-pdf here. The two runs
are checked SEPARATELY rather than pooled: an anchor that exists only in the HTML
branch must not satisfy a reference made in the PDF branch. Findings name the
branch they came from.
A/B with a dangling xref planted inside the backend-pdf branch of
Native-Themes.asciidoc:
old "Cross-references OK: 1741 anchors" -- exit 0
new "Native-Themes.asciidoc:506: <<definitely-not-a-real-anchor>> ...
(1 reference(s), pdf render)" -- exit 1
check-missing-code-blocks.py accepted any `[attribute]` line as the start of the
promised block, so an admonition standing where a listing used to be hid the
hole. An admonition is prose and can never be the listing a sentence promised, so
the five names are excluded. Excluded by name rather than by whitelisting the
kinds that ARE code: measured, the bracket lines legitimately answering a
promising sentence already span [source] (582), [cols=...] and [options=...] (56),
[listing] (3), [quote] (2) and an anchored image, and a whitelist would report the
next kind nobody anticipated.
A/B with each block planted after a promising sentence, using the real signature
of a removed listing -- the two blank lines it leaves behind:
[NOTE] old: none new new: reported as a hole
[source,java] old: none new new: none new
The baseline is unchanged at 402, because the guide currently has no admonition
sitting in a listing's place.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…separator
Two findings, and this time the first one is live.
The rendered book contains exactly three relative hrefs, and all three are
broken: css.asciidoc carried link:Images[], link:Fonts[] and
link:Supported-Properties#text-decoration[] over from the wiki this guide
replaced, naming pages that were never carried across with them. The guide
renders as a single page, so a relative href resolves against wherever that page
is served and reaches nothing that ships with it -- the reader gets a 404 while
every gate reported success. All three targets are sections in that same file:
[[Images]] already existed, [[Fonts]] is added to match it, and
[[text-decoration]] was already there and already used by a sibling reference.
check-guide-xrefs.py now reports any relative href, which after this change has
zero instances. Absolute URLs stay with check-guide-links.py, and root-relative
paths and fragments are excluded. A/B with one link restored: old printed
"Cross-references OK" and exited 0, new reports it and exits 1.
The second is a wrong claim in my own comment. check-guide-structure.py returned
early when an include was followed by ifdef/ifndef/ifeval/endif, on the reasoning
that "a preprocessor directive is not content and cannot absorb a paragraph".
True, and irrelevant: asciidoctor REMOVES the directive during preprocessing, so
it does not separate anything either. Measured -- with `ifdef::backend-html5[]`
between an include ending in prose and a following heading, the render contains
zero heading tags and the paragraph reads "...preferred tools. === A parent-local
heading". The rule now scans past directive lines to the first line that survives
preprocessing. A/B on that construct: old passed it, new reports it.
Note the rendered-title check cannot cover this case either, because a
parent-local heading is not the first heading of any included file.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:f9e50cb8cc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-links.py
Comment threadscripts/developer-guide/check-guide-xrefs.py
… has a limit
The first finding was live: the link ratchet held a working link.
docs/website/functions/[[path]].js runs after context.next() has already 404ed
and then redirects anything under /files/ or /demos/ to
download.codenameone.com. Those paths are served, so the model was missing a
whole class of route and had banked a real one -- /files/iOS_UI-Kit.psd -- as
broken. The destination is off-site, which puts it in the same bucket as every
other off-site redirect: reachable, not verifiable from this repository. The
rules are appended AFTER the _redirects rules because the function is a fallback
and the first matching rule wins, mirroring how the host evaluates. The baseline
shrinks 37 -> 36, and a new /files/ link is now accepted rather than failing CI.
The second is a real limit of the PDF surrogate, and one the command line cannot
remove. Setting backend-pdf makes ifndef::backend-pdf[] content disappear and
ifdef::backend-pdf[] content appear -- verified, and that is the only form this
guide uses. It does not undefine backend-html5: the HTML converter sets that
itself, after command-line attributes are applied, so even `-a backend-html5!`
leaves it defined. Measured both ways. Content guarded on the HTML backend would
therefore survive into the surrogate render and could satisfy a PDF-only
reference that the real asciidoctor-pdf build leaves dangling.
The guide has no such conditional, so rather than model a construct that is not
there -- or drive asciidoctor-pdf and try to read anchors back out of a PDF --
the checker refuses one if it appears, naming the alternative the rest of the
guide already uses. Probed: planting an ifdef::backend-html5[] block aborts the
check with that message.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:6a8963d02a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-structure.py Outdated
…side one
Both scanners matched a delimiter of exactly four characters, so a block opened
with ----- was invisible to them and its contents were scanned as live markup.
The guide has 20 such lines -- eight in Desktop-Integration, six in
Working-With-iOS, six in appendix_goal_generate_archetype -- every one an
ordinary [source] block wrapping an include::. validate-guide-snippets.py already
accepted -{4,}; these two had drifted from it.
Nothing is misread today, because those includes name .java, .xml and .properties
files and the reachability walk skips non-asciidoc targets. The failure mode is
still the interesting one though: it is a FALSE POSITIVE generator, not a missed
defect. A/B with each construct planted inside a five-dash block:
a displayed include::Working-with-UWP.asciidoc[]
old reported a swallowed-heading error against markup that only appears
on the page as text
new clean
a displayed "The wrapper looks like this:" followed by blank lines
old reported it as a missing code block
new clean
Both now track the delimiter that opened the block and close on one of the same
character AND length, which is what asciidoctor does -- so a four-dash line
inside a five-dash block is content rather than the close. The set stays limited
to literal blocks: an example (====) or sidebar (****) block contains live
markup, so headings and includes inside one are real and must keep counting.
Baselines unchanged: 119 included documents, 402 holes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:57212b7f43

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-structure.py
Comment threadscripts/developer-guide/check-guide-xrefs.py
shai-almogand others added 2 commits September 2, 2026 18:21
* Guide figures render with a bundled font instead of the host's
None of the 24 generated figures could be reproduced outside CI. Running the
generator on a Mac reproduces 0 of 24 byte-for-byte, with 3-20% of pixels
differing. Two consecutive local runs are byte-identical, so the generator is
deterministic on one host; the variable is the font.
`PreAdvancedThemingScreenshots` styled with `Font.createSystemFont`, which
resolves through `JavaSEPort.fontFaceSystem` -- "Arial" on macOS and Linux
alike. Arial exists on a developer's Mac and not on a stock CI runner, so AWT
silently substitutes and every glyph changes. The differences are exactly that:
for `flow-layout.png` every differing pixel sits in y 10-130, the title and
label rows, while the colored blocks below match to the pixel.
So the byte-exact gate has only ever been green because CI both generates and
verifies. A developer regenerating locally could not match it, and at the scale
the guide's remaining ~260 app screenshots would need, that is untenable.
The figures now load `native:MainRegular`, which `JavaSEPort.loadTrueTypeFont`
reads from `/com/codename1/impl/javase/Roboto-Medium.ttf` on the classpath
rather than from an installed-font lookup. This is also what the project's font
rule requires everywhere: never `createSystemFont`, always the `native:` scheme.
A null return refuses loudly rather than falling back to a host font, because a
silent fallback would restore the exact non-determinism this removes.
The committed baselines are regenerated from a Mac. CI byte-compares them on
Linux, so the check either passes -- proving host independence rather than
asserting it -- or fails and says so immediately.
Composition, dimensions and colours are unchanged; only the typeface moves.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Pin the font on every component, not just the ones that get block colours
CI reported one stale figure out of 24: `guibuilder-2-insets-3.png`, the only
one containing a `TextField`. The other 23 matched Linux byte for byte, so the
bundled-font change worked -- it just did not reach far enough.
`applyBlockStyleToContent` styled `Label` and `Button`. Everything else kept the
theme's default font, which resolves through the host, so the text field and its
hint were still host-dependent. Enumerating the types that carry text would have
left the next one added broken the same way, so the walk now pins the face on
every component it visits, plus the hint label, which is painted by a `Label`
that is not in the component tree and so is never reached by the walk.
The first attempt used `BLOCK_FONT` for this and regressed the figure: at 29px
against the theme default's 13px the field grew and squeezed "Submit" down to a
clipped sliver. `FIELD_FONT` is sized to reproduce the original height, so the
composition is unchanged and only the typeface moves.
Verified locally by rendering twice, once with `JavaSEPort.setFontFaces` pointed
at a family that does not exist -- which is what a machine without Arial looks
like to the port. All 24 come out byte-identical, so nothing in these figures
reads an installed font any more.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Publish what the runner actually rendered when a screenshot fails
"Committed screenshot is stale: <name>" names the file and nothing else, so
there is no way to tell a real regression from an environment difference
without adding a debugging round trip to CI. The generated directory is now
uploaded as an artifact when the step fails.
Needed immediately: one figure still differs between a Mac and the runner after
the font fix, and host fonts, JDK version and working directory have each been
ruled out locally.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Pin MigLayout's platform, and stop demanding byte equality of a glyph edge
The artifact step paid for itself immediately. Downloading what the runner
rendered showed two figures differing, not the one the gate reported -- it exits
on the first mismatch -- and the two had nothing in common.
`mig-layout.png` differed by 12.8% of its pixels, a real layout shift.
MigLayout takes its default gaps from `PlatformDefaults`, which reads
`System.getProperty("os.name")` and answers MAC_OSX, GNOME or WINDOWS_XP, each
with different spacing. The figure was rendering with macOS gaps on a Mac and
GNOME gaps on the runner. Pinning the platform fixes it: with the pin, 23 of the
24 figures now match the runner's own output byte for byte.
`layered-layout.png` differed by 173 pixels, 0.113%, inside a 25x25 box. That
one is not fixable. Measured against the runner's bytes, the material glyph
lands at exactly the same size and the same origin -- a 55x49 bounding box --
and differs only in antialiased edge coverage, 946 fully-white pixels against
916. Java2D rasterizes the same glyph, from the same bundled font, at the same
size, slightly differently on the two platforms. Demanding byte equality there
would mean deleting legitimate content from the figure or carrying a
permanently red check.
So the comparison moves from `cmp -s` to a comparer that still requires byte
equality by default and accepts a bounded difference only where a figure carries
a `.tolerance` sidecar explaining itself, in the same key=value shape the CN1SS
suites already use. The area bound does the work: a per-pixel delta that large is
meaningless on its own, since a glyph edge flips between white and the block
behind it, but a regression that changed the icon would move far more than 0.3%
of the image.
Verified by running the comparer with the runner's own output against the
committed figures -- which is exactly what CI will do -- and by four probes: an
untoleranced figure that differs fails, the toleranced figure fails when changed
beyond its budget, a missing figure fails the count, and a clean run passes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Guard the iOS font branch, and stop the comparator gating itself out
Two review findings on this PR.
The first says `native:MainRegular` does not give host-independent output,
because `loadTrueTypeFont` resolves native fonts to the first installed SF or
Helvetica family before reaching the bundled Roboto. The branch is real, but it
is reached only when `isIOS` is set, which `loadSkinFile` does for a skin whose
systemFontFamily contains "helvetica" -- and this generator never loads a skin.
The measurement agrees: figures rendered on a Mac match the Linux runner byte
for byte, which could not happen if one side were resolving Helvetica Neue and
the other Roboto.
So the conclusion does not hold today, but the risk is real for tomorrow: a
change that loads a skin here would put host fonts back into the output with no
other symptom. The generator now refuses to run under an iOS platform, and says
why, rather than leaving that to a comment nobody reads.
The second finding is straightforwardly right and is the more serious of the
two. `on.pull_request.paths` triggers on `scripts/developer-guide/**`, but the
`Determine changed components` filter named only two scripts, so a pull request
touching any other script here started the workflow with `docs` false and
skipped the steps that script governs. A change to compare-screenshots.py could
have merged without the screenshot check ever running it -- a gate that skips
itself. The filter now covers the whole directory, which also removes the
two-copies-drift the surrounding comment already warns about.
Verified the guard changes no output: all 24 figures are unchanged, and the
comparer still passes against the runner's own bytes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Count every changed pixel toward the area budget, not just the loud ones
The comparator incremented its mismatch counter only for pixels whose channel
delta EXCEEDED maxChannelDelta, which is what the CN1SS comparator does. With a
sidecar written the way this one was -- a large delta bound paired with a small
area bound -- that leaves an unbounded hole, and review gave the exploit in the
figures' own palette: recolouring the green #06a806 to #a608a6 moves every
channel by exactly 160, so with maxChannelDelta=160 not one pixel is counted and
a dramatically different image reports zero mismatches.
Reproduced it before fixing: the recolour changes 4191 pixels, 2.73% of the
image, at a worst delta of exactly 160 -- and passed.
The two bounds are now independent. maxMismatchPercent limits how much of the
image may change at all, counting every differing pixel; maxChannelDelta caps how
far any single pixel may move. The measured legitimate noise -- 173 pixels,
0.113%, worst delta 141 -- still passes, the recolour now fails on area, and a
five-pixel solid overwrite fails on delta.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Correct the sidecar's rationale to match how the bounds now work
The previous commit changed the comparator so every differing pixel counts
toward the area budget and the channel delta is a separate ceiling. The sidecar
still explained the old behaviour -- that the area bound did the work and the
delta was meaningless on its own -- which is now wrong in a file whose whole
purpose is to justify the numbers beside it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Compare figures in RGBA, because they have an alpha channel
The comparator converted both images to RGB before counting differences. The
committed figures are genuine RGBA PNGs -- four channels, currently opaque
everywhere -- so that dropped a real channel, and any change confined to alpha
was invisible to the tolerance path. A regression that turned the whole figure
transparent while leaving every colour channel intact reported zero changed
pixels and passed.
Byte equality, which every figure without a sidecar is still held to, always
caught this. Only a figure carrying a sidecar could reach the weakened path, so
today the exposure was one image -- but that image is exactly the one whose
comparison is relaxed.
Verified: making layered-layout.png fully transparent with its RGB channels
untouched now reports 100% of pixels changed, the real runner output still
passes, and the recolour and untoleranced-difference probes still fail as they
did.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…page
Two findings, one of them a false positive I introduced last round.
Comment blocks. Asciidoctor drops a //// block entirely, so an include inside
one does not put its target in the book and prose inside one promises the reader
nothing -- but both scanners read straight through. //// joins the fence set in
each. A/B with each construct planted inside a comment block: a displayed
include:: made the structure check fail, and a promising sentence made the
missing-block check fail; both are clean now.
Note this is deliberately NOT symmetric with check-guide-links.py, which still
reads URLs inside comments. The reasons differ and the code says so: a dead link
commented out is still dead debt in the source, and letting the ratchet shrink
for it would make "comment the line out" a way to silence that gate. Reachability
is the opposite -- calling a commented-out include reachable states something
about the book that is simply untrue.
Packaged assets. The relative-href rule added last round was too broad: the HTML
packaging step copies every subdirectory of docs/developer-guide next to
developer-guide.html, so link:img/example.png[] resolves for a reader who opens
the zip, and the gate would have rejected it. A relative href that names an
existing file under the guide directory is now allowed. sketch/ is not, because
that is the one directory the packaging loop skips.
Probed all four ways, since an exemption is only worth having if it still
rejects:
link:img/flow-layout.png[] exit 0 (real, packaged)
link:img/does-not-exist.png[] exit 1
link:sketch/whatever.svg[] exit 1 (not packaged)
link:Supported-Properties[] exit 1 (the wiki-era shape)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:a6bd5197dc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadscripts/developer-guide/check-guide-xrefs.py Outdated
Comment threadscripts/developer-guide/check-guide-xrefs.py
shai-almogand others added 2 commits September 2, 2026 18:35
Both findings are defects in the two rules added in the previous commit.
packaged_asset() asked whether the file exists here, which is not the question. A
link only works if the published output carries it, and the two outputs filter
differently:
* the HTML archive copies only the SUBDIRECTORIES of docs/developer-guide next
to developer-guide.html and skips sketch, so a root-level file is never in the
zip however much it exists in the tree;
* the website rsync excludes sketch/, *.asciidoc and *.adoc, so a source file
nested inside a packaged directory still does not reach the site.
So link:Introduction.asciidoc[] was being exempted and readers would follow it to
nothing. Both filters are now reproduced. The permissive direction is the
expensive one here -- it suppresses a finding for a link nobody can follow --
so all four cases are probed:
img/flow-layout.png exit 0 in both outputs
Introduction.asciidoc exit 1 in neither
img/does-not-exist.png exit 1
sketch/foo.svg exit 1 excluded by both
The backend-conditional rejection scanned raw lines, so a source block DISPLAYING
`ifdef::backend-html5[]` as example text aborted the whole gate. A displayed
directive takes no part in preprocessing. It now tracks literal and comment block
delimiters the way the other guide scanners do. A/B: displayed inside a source
block went 1 -> 0, while a genuinely active conditional still exits 1.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…18 statistics (#5671)
* The Introduction taught a dead IDE menu, an iTunes walkthrough and 2018 statistics
Six corrections to the chapter a new developer reads first:
* The JavaScript target was described twice and contradicted itself: the
overview said TeaVM did the translation, the port section said ParparVM with
TeaVM as a fallback. The port section is right -- `javascript.port` is
declared with `parparvm` as its default and `teavm` documented as "the
original builder as a compatibility fallback".
* `http://teavm.org:[TeaVM-based builder]` -- the stray colon made the macro a
bare host, and the site serves TLS. Removed from the link ratchet, which now
stands at 37.
* Bitcode was cited as something ParparVM absorbed without modification. Apple
has since withdrawn bitcode, which makes the point better than the arrival
did, so the sentence now names both moves.
* The device-fragmentation section rested on two 2018 Android share numbers,
followed by a sentence conceding they would be stale on arrival. The
structural claim survives without them.
* The iOS developer fee was dated with "for 10 years at the time of this
writing".
* The UDID instructions walked the reader through iTunes, with a screenshot of
iTunes on iOS 9.3.5 that also exposed a real device serial and UDID. iTunes
has not existed on macOS since Catalina, and the signing chapter already gives
the current answer, so this now points there.
* The device build was "a right click away" via the IDE plugin, illustrated by a
menu still offering Blackberry, J2ME and Windows Phone builds. Replaced with
the `cn1:buildAndroid` and `cn1:buildIos` goals, taken from the mojo names.
Both screenshots are deleted, so find_unused_images.py stays satisfied.
Verified: asciidoctor at --failure-level WARN, Vale at suggestion level,
LanguageTool (status ok, 0 matches, run under JDK 17 rather than the JDK 8
false green), paragraph capitalization, snippets, xrefs, structure, links and
missing-code-blocks all clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* "-pl common" made a device build a no-op that reported success
Review caught the command I had just written. AbstractBuildWrapperMojo.execute()
opens with:
if (!project.isExecutionRoot()) {
getLog().info("Skipping execution for non-root project");
return;
}
and BuildAndroidMojo, BuildIosMojo and BuildWin32Mojo all inherit it unchanged.
Under `-pl common` the selected module is not the execution root, so the goal
logs that line and stops. The design is deliberate -- the wrapper re-invokes
Maven on the root pom itself (`request.setPomFile(new File("pom.xml"))`) -- so
naming a module both skips the wrapper and defeats its purpose.
Measured rather than reasoned, against scripts/hellocodenameone:
mvn -pl common ...:buildWin32 -> "Skipping execution for non-root project"
BUILD SUCCESS, nothing built
mvn ...:buildWin32 -> no skip line, proceeds into the reactor
A silent success is the worst failure mode for a getting-started instruction, so
the Introduction now says where to run it and warns about the module form
explicitly.
Working-With-Windows carried the same `-pl common` shape and is fixed with it --
it is where I copied the form from, so leaving it would reintroduce the defect
the next time someone follows the pattern.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* The Windows build wrappers named a platform no profile answers to
Review checked the command this PR documents and found it still builds nothing.
The wrapper passes `codename1.platform` to its nested Maven run, and that
property is what activates the module profile in a generated project's root pom.
The profile is `win`, matching the value the win module itself declares:
<profile><id>win</id>
<activation><property>
<name>codename1.platform</name><value>win</value>
</property></activation>
<modules><module>win</module></modules>
BuildWin32Mojo and BuildWindowsDeviceMojo both passed "windows", which matches no
profile at all. Nothing else in the plugin reads the platform as "windows", so
the value was simply inert: the win module never joined the reactor and the
nested build reported success having produced no Windows binary. Every other
wrapper already agrees with its profile -- android, ios, javascript, linux and
javase all match -- so these two were the only ones out of step.
Verified by A/B against scripts/hellocodenameone, reading the NESTED reactor
rather than the outer one:
before hellocodenameone, -common, -javase <- no win module
after hellocodenameone, -common, -win
The build TARGET is a separate namespace and stays "windows-device"
(Executor.BUILD_TARGET_WINDOWS_NATIVE), which is correct.
SpotBugs over codenameone-maven-plugin regenerated: 0 findings.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* The primary Win32 command had both defects the convenience goal just lost
Fixing BuildWin32Mojo made the sentence beside this snippet false: the goal now
submits a build, while the command the chapter leads with still could not. It
carried both defects at once --
mvn -pl common package -Dcodename1.platform=windows ...
-- `-pl common` builds that module instead of the reactor, so the win module is
never reached, and `windows` matches no profile even when the reactor is whole.
The two mistakes hid each other: with only the module selected, the platform
value had nothing left to activate.
Corrected to what the wrapper actually runs, read off AbstractBuildWrapperMojo:
goal `package`, `codename1.platform=win`, `codename1.buildTarget=windows-device`,
from the project root.
A/B against scripts/hellocodenameone, reading the reactor, which Maven prints
before it compiles:
old no win module in the reactor at all
new hellocodenameone, -common, -win
Both invocations then fail identically on this machine with "invalid target
release: 17", because tools/env.sh pins JDK 8 while the demo targets 17. That is
environmental and equal on both sides, so it does not affect the comparison.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@shai-almog