Developer guide: settle the seven Monetization sentences that promise code - #5810
Conversation
… code The chapter had the largest cluster of holes in the guide: seven sentences ending in a colon with nothing after them, left over from the snippet extraction. Two kinds, handled differently, because they are different problems. Two are ordinary Codename One code and are restored as compiled snippets: the button that calls synchronizeReceipts, and the main-form label that reads getExpiryDate after a sync. Both recovered from bbdc605~1 and now compile with the rest, so they cannot rot again. Five describe server-side JAX-RS and JPA, or client code built on the RESTfulWebServiceClient cn1lib. None of it compiles in a Codename One module, which is why it was dropped and why restoring it would mean either a second build or an inline block the snippet validator rejects. Those sentences now point at the two demo projects the chapter already links, which is where the code actually lives and stays current. The NetBeans web service wizard goes with them. It generated the controller years ago, it is not how anyone would produce one now, and the sentence was carrying it purely as provenance. Ratchet drops from 34 to 27. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5a962a2041
ℹ️ 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".
synchronizeReceipts reports true only when every pending purchase reached the receipt store AND the receipts came back, so the expiry-label listing was presenting a stale cached status as a fresh answer whenever the network leg failed. It now branches on the flag and says which it is showing; the reason is in a comment beside the branch, because the trap is in the callback's contract rather than in this code. Both listings also built a component and dropped it. A reader copying the manual-sync listing got a Button that is never added to anything and a listener that can never run. Both now add what they build to the form. And the CN1-IAP-Validator section still opened its follow-up with "As you can see from this snippet" after the snippet became a pointer to the server project. It now names the project. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1824002e60
ℹ️ 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".
The prose says the main form shows the current expiry date and the button is there for a user who thinks that value is out of date, but the listing set the label only from inside the listener, so a form nobody pressed sat on "Loading rental details..." forever. The receipts already on the device answer the question with no round trip, so the label is now filled in as the form is built and the sync path reuses the same method. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 499df394aa
ℹ️ 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".
… had The failure branch replaced the expiry label with an error string that said it was showing the last known status, which is the one thing it was not doing. Nothing was reloaded on that path, so there is nothing new to put in the label: the error goes to a ToastBar and the label keeps the answer the receipts already gave. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f8c8ce092f
ℹ️ 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".
The chapter synchronizes receipts in two places -- once at the end of start(), once behind a manual button -- and only the button's path was touching the expiry label. Stale receipts at launch therefore stayed on screen until the user pressed a button they had no reason to press, which is exactly the case the automatic sync exists to cover. The label is now a field rather than a local, and both callbacks call the same showRentalStatus(). The start() listing's callback stops being a comment and does the thing the comment described. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Cloudflare Preview
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5cce9e5742
ℹ️ 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".
The chapter shows the manual button, then shows it again as part of the expiry-label listing, so a reader following it in order builds two of them and only the second one refreshes anything. There is only one button in the app being described, so there is now only one in the listings. The button listing owns it and calls the same showRentalStatus() that start() calls; the expiry listing is reduced to what it is actually introducing -- the label, the field it lives in, and the method that paints it from the receipts already on the device. The sentence above it said the reader would add a button there, which is why the second one existed at all. It now says what the listing does. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: bb209355c2
ℹ️ 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".
hi is a local of start() in the hello-world app the chapter builds, and the Form field these snippet wrappers carry sits outside the AsciiDoc tag, so the published listings referred to something a reader copying them cannot see and could not compile. Both helpers now take the Form. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 99bc5b2c45
ℹ️ 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".
Following the listings in order produced a form with neither the expiry label nor the sync button on it, because the only listing that runs at startup did the synchronization and nothing else. start() now builds the form and calls both helpers, so the three listings assemble into the app the chapter describes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c586ed88ad
ℹ️ 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".
|
Developer Guide build artifacts are available for download from this workflow run:
Developer Guide quality checks: |
Every listing from the non-renewable-subscription section onward calls
iap.isSubscribed(PRODUCTS) or iap.getExpiryDate(PRODUCTS), and no published
block ever declared either name -- the snippet wrappers carry them outside
the tag, so the code in the book referred to something a reader could not
see. The prose introduces PRODUCTS in the same breath ("the example uses an
array"), and the paragraph that follows it opens with "Notice that you
create two separate SKUs", which is a sentence about a listing that was no
longer there.
The three declarations now appear where the chapter introduces them, once,
ahead of every listing that uses them.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ca3a585aaf
ℹ️ 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".
Codename One calls start() again when the app is resumed, and the listing built a fresh Form every time and handed it components the first form still owns -- insertComponentAtImpl rejects a component that already has a parent, so the app the chapter describes throws on resume. The earlier start() listing in this same chapter already carries the current-form guard; this one now does too. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7060a2daa9
ℹ️ 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".
…aunch does The guard I added last round returned before the synchronization, so a resume showed the old form and never refreshed it -- and a resume is exactly when a subscription bought, renewed or cancelled on another device becomes visible. The sync now sits outside the branch: the launch builds the form, the resume shows the one that survived, and both fall through to it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a4d7c32289
ℹ️ 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".
itemPurchased already reloads the receipts and then only showed a toast, so the form behind it kept saying the user has no subscription right after they bought one -- until a manual sync or a resume. It now calls the same showRentalStatus() the other two paths call, and revalidates. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0310641f7e
ℹ️ 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".
Receipts are cached on the Purchase instance -- the field is per-object and loaded lazily -- and most ports hand back a fresh instance from every getInAppPurchase() call. The purchase callback made its own, synchronized that, and then repainted from the shared one, which had not been touched: the label kept the answer it had before the purchase, which is exactly the bug the repaint was added to fix. It now uses the shared instance the rest of the chapter declares. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0940e4429e
ℹ️ 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".
Both listeners held their own getInAppPurchase(), so after a purchase synchronized the shared instance the next click still described the state from before it -- "you're already renting the world until <old date>", or no subscription at all. They use the shared field now, and their purchase() calls go through it as well rather than making yet another instance. The one-time-purchase listings earlier in the chapter are left alone on purpose: wasPurchased() is implemented per port and does not read the instance receipt cache, so nothing there goes stale. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fb043b0f4a
ℹ️ 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".
Two rounds of this thread moved toward sharing one Purchase across the chapter. Reading the class settles it the other way. synchronizeReceipts coalesces on a static syncInProgress, so a second call while the port's own synchronization is running registers its callback and returns without loading anything. The receipts it waits for land in the instance the port created, via setReceipts, which writes the instance field AND persists to storage. A held instance never sees them; its cache is loaded once, lazily, and nothing invalidates it. The shared state is therefore the persisted receipts, not a shared object. Every use site now takes a fresh getInAppPurchase(), whose cache loads from storage on first read -- after the synchronization has written there. That is a storage read rather than a network call, and it is what the ports themselves do. The declarations listing drops the field and says why, and the purchase callback notes that nothing reads receipts off its instance until after the synchronous sync, which is what makes the order work. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d51458ee10
ℹ️ 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".
iOS adds its StoreKit transaction observer while the view controller is being set up, so an unfinished transaction is re-delivered to itemPurchased() before start() has built anything. The listing then dereferenced a form that did not exist yet. The label is a field and is safe to set, so the status still gets written; only the revalidate is guarded, and start() paints from the same method when it does run. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b56b53309f
ℹ️ 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".
synchronizeReceipts coalesces on a static syncInProgress, so a purchase completing mid-fetch queues its receipt through postReceipt and cannot start a synchronization of its own. The fetch already in flight was requested before that receipt existed, and onLoadReceiptsComplete reported its success to every waiting caller -- so the caller got a snapshot that predates the purchase and the receipt stayed pending until something synchronized again. On a subscription app that is a purchase the user made and the app does not show. onLoadReceiptsComplete now re-runs when the pending queue is non-empty, the same way onSubmitReceiptComplete already continues draining. The registered callbacks are untouched and fire from that later pass, so the existing "callback fires exactly once" guarantee holds. The receiptStore null check is load-bearing rather than defensive: with no store, synchronizeReceipts skips the submit branch and comes straight back here with the queue still non-empty. There is already a test in this suite for the sibling infinite-recursion case, which is what made the hazard obvious. Found while checking a developer-guide listing that repaints a subscription label from this callback. Verified by reverting the fix: the new test fails with "expected: <1> but was: <0>" -- the receipt never submitted -- and passes with it, 18/18 in the class either way otherwise. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Compared 151 screenshots: 151 matched. Native Android coverage
✅ Native Android screenshot tests passed. Native Android coverage
Benchmark ResultsDetailed Performance Metrics
|
✅ Continuous Quality ReportTest & Coverage
Static Analysis
Generated automatically by the PR CI workflow. |
|
Compared 181 screenshots: 181 matched. |
|
Compared 160 screenshots: 160 matched. Benchmark Results
Detailed Performance Metrics
|
|
Compared 148 screenshots: 148 matched. Benchmark Results
Detailed Performance Metrics
|
|
Compared 144 screenshots: 144 matched. |
|
Compared 217 screenshots: 217 matched. |
Monetization had the largest cluster of missing code blocks in the guide — seven sentences ending in a colon with nothing after them, left from the snippet extraction in
bbdc6058f0.They are two different problems and are fixed two different ways.
Restored as compiled snippets (2)
Ordinary Codename One code, recovered from
bbdc6058f0~1:synchronizeReceiptsgetExpiryDateafter a syncBoth now live in the demos module and compile with everything else, so they cannot silently rot again.
Pointed at the source (5)
The rest describe server-side JAX-RS and JPA, or client code built on the
RESTfulWebServiceClientcn1lib. None of it compiles in a Codename One module — which is why it was dropped, and why restoring it would mean either standing up a second build or writing an inline block thatvalidate-guide-snippets.pyrejects.Those sentences now point at the two demo projects the chapter already links, which is where that code actually lives and stays current. A promise of a listing that cannot be kept is worse than a pointer to the real thing.
The NetBeans web service wizard reference goes with them: it generated the controller years ago, it is not how anyone would produce one today, and the sentence carried it purely as provenance.
Result
check-missing-code-blocksratchet drops 34 → 27, and the gate confirmed all seven were settled before the baseline was rewritten.validate-guide-snippets.py— 1109 include-backed blocks, up from 1107, and the two new snippets compileasciidoctor --failure-level WARNandasciidoctor-pdf— cleanstatus: ok,total: 0