Skip to content

refactor: codebase improvements across docs, google, and gql packages#82

Merged
hyochan merged 6 commits intomainfrom
refactor/codebase-improvements
Mar 20, 2026
Merged

refactor: codebase improvements across docs, google, and gql packages#82
hyochan merged 6 commits intomainfrom
refactor/codebase-improvements

Conversation

@hyochan
Copy link
Member

@hyochan hyochan commented Mar 20, 2026

Summary

  • Refactor SearchModal by extracting search data to separate module
  • Deduplicate Google Android flavor code by consolidating shared helpers and ViewModel
  • Add GQL schema convention linter to validate naming rules before code generation

Changes

Documentation (packages/docs)

  • Extract searchData.ts with API data previously inline in SearchModal
  • Add useCallback memoization to SearchModal handlers

Android (packages/google)

  • Move OpenIapViewModel to main source set (shared across flavors)
  • Extract CommonHelpers.kt with shared suspend functions
  • Remove duplicated code from flavor-specific Helpers.kt and SharedHelpers.kt

GQL (packages/gql)

  • Add schema-linter.ts that validates iOS/Android suffix conventions, union/future markers
  • Integrate linter into codegen pipeline (runs before generation)
  • Mark unused template-plugin.ts as @deprecated

CI/CD

  • Add prettier format check for docs package

Test plan

  • bun run typecheck passes (docs)
  • ./gradlew :openiap:compilePlayDebugKotlin passes
  • ./gradlew :openiap:compileHorizonDebugKotlin passes
  • bun run generate passes (gql)
  • Schema linter validates existing schema without false positives (0 errors, 8 warnings)

🤖 Generated with Claude Code

- Refactor SearchModal (915→167 lines): extract apiData to searchData.ts,
  keyboard logic to useSearchKeyboard hook, add useCallback memoization
- Deduplicate Google Android flavors: move OpenIapViewModel to main source
  set, extract CommonHelpers.kt with shared suspend functions
- Add GQL schema convention linter: validates iOS/Android suffixes,
  union/future markers before code generation
- Add prettier format check to CI/CD for docs package
- Align signal pattern to object-based format per docs conventions
- Mark unused template-plugin.ts as @deprecated

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@gemini-code-assist
Copy link

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request focuses on enhancing code quality, maintainability, and developer experience across multiple packages. It streamlines the documentation search functionality by modularizing its components, reduces code duplication in the Android implementation by centralizing shared logic, and introduces a robust schema linter to enforce GraphQL naming conventions and improve the reliability of code generation. These changes collectively contribute to a more organized, efficient, and error-resistant codebase.

Highlights

  • Documentation Search Modal Refactor: The SearchModal component in the documentation package has been significantly refactored, reducing its size from 915 to 167 lines. This was achieved by extracting search data into a dedicated file (searchData.ts) and keyboard navigation logic into a custom hook (useSearchKeyboard). Event handlers now utilize useCallback for memoization, and the search modal signal pattern has been aligned to an object-based format for better extensibility.
  • Android Code Deduplication: Common helper functions and the OpenIapViewModel for Google Android flavors have been consolidated. The OpenIapViewModel was moved to the main source set, and shared suspend functions were extracted into a new CommonHelpers.kt file, eliminating duplicated code across flavor-specific Helpers.kt and SharedHelpers.kt files.
  • GraphQL Schema Linter: A new GraphQL schema convention linter has been introduced to validate naming rules (e.g., 'IOS' and 'Android' suffixes for platform-specific types/fields) and special markers (union, future) before code generation. This linter is integrated into the codegen pipeline, ensuring schema consistency and preventing generation issues.
  • CI/CD Improvements: A Prettier format check has been added for the documentation package to enforce consistent code styling.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Ignored Files
  • Ignored by pattern: .github/workflows/** (1)
    • .github/workflows/ci.yml
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces several significant improvements across the codebase. The refactoring in the docs package, particularly extracting the search data and keyboard logic from SearchModal, greatly improves maintainability and readability. The deduplication of code in the google package by moving shared helpers and the ViewModel to the main source set is also a very positive change. The addition of a schema linter in the gql package is a proactive step towards ensuring schema consistency.

I have found one critical issue related to the refactoring in the google package, where a field required for alternative billing seems to have been dropped. Please see the detailed comment.

The developerBillingOption field was dropped during the extraction of
AndroidPurchaseArgs from Helpers.kt to CommonHelpers.kt, causing
compilePlayDebugKotlin to fail with unresolved reference.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 20, 2026

Warning

Rate limit exceeded

@hyochan has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 3 minutes and 44 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b7e2f68c-43c1-48db-8d39-94101da67f91

📥 Commits

Reviewing files that changed from the base of the PR and between 3e1e05f and ac65977.

📒 Files selected for processing (38)
  • packages/docs/src/components/CodeBlock.tsx
  • packages/docs/src/components/LanguageTabs.tsx
  • packages/docs/src/components/Pagination.tsx
  • packages/docs/src/components/SearchModal.tsx
  • packages/docs/src/lib/searchData.ts
  • packages/docs/src/pages/404.tsx
  • packages/docs/src/pages/docs/android-setup.tsx
  • packages/docs/src/pages/docs/apis/android.tsx
  • packages/docs/src/pages/docs/apis/connection.tsx
  • packages/docs/src/pages/docs/apis/debugging.tsx
  • packages/docs/src/pages/docs/apis/index.tsx
  • packages/docs/src/pages/docs/apis/ios.tsx
  • packages/docs/src/pages/docs/apis/products.tsx
  • packages/docs/src/pages/docs/apis/purchase.tsx
  • packages/docs/src/pages/docs/apis/subscription.tsx
  • packages/docs/src/pages/docs/apis/validation.tsx
  • packages/docs/src/pages/docs/errors.tsx
  • packages/docs/src/pages/docs/events.tsx
  • packages/docs/src/pages/docs/features/external-purchase.tsx
  • packages/docs/src/pages/docs/features/purchase.tsx
  • packages/docs/src/pages/docs/features/subscription-upgrade-downgrade.tsx
  • packages/docs/src/pages/docs/features/subscription.tsx
  • packages/docs/src/pages/docs/guides/ai-assistants.tsx
  • packages/docs/src/pages/docs/horizon-setup.tsx
  • packages/docs/src/pages/docs/lifecycle/subscription.tsx
  • packages/docs/src/pages/docs/types/alternative.tsx
  • packages/docs/src/pages/docs/types/android.tsx
  • packages/docs/src/pages/docs/types/offer.tsx
  • packages/docs/src/pages/docs/types/product.tsx
  • packages/docs/src/pages/docs/types/purchase.tsx
  • packages/docs/src/pages/docs/types/request.tsx
  • packages/docs/src/pages/docs/types/verification.tsx
  • packages/docs/src/pages/docs/updates/announcements.tsx
  • packages/docs/src/pages/docs/updates/notes.tsx
  • packages/docs/src/pages/introduction.tsx
  • packages/docs/src/pages/tutorials.tsx
  • packages/docs/src/styles/documentation.css
  • packages/docs/src/styles/pages.css
📝 Walkthrough

Walkthrough

This pull request consolidates documentation search infrastructure, refactors modal keyboard handling into a reusable hook, reorganizes Android purchase helper implementations, removes a ViewModel wrapper, adds formatting checks to CI, and introduces a GraphQL schema linter.

Changes

Cohort / File(s) Summary
Docs Formatting & CI
.github/workflows/ci.yml
Added Prettier format check step (bunx prettier --check) to the test-docs job to validate TypeScript, TSX, and CSS file formatting before build.
Docs Search Modal & Signals
packages/docs/src/App.tsx, packages/docs/src/components/SearchModal.tsx, packages/docs/src/lib/signals.ts
Refactored searchModalSignal from boolean to object-based ({ isOpen: boolean }). Extracted keyboard and navigation logic from SearchModal into a custom hook. Removed inline apiData and listener management from SearchModal and delegated keyboard event handling to the new useSearchKeyboard hook.
Docs Search Infrastructure
packages/docs/src/hooks/useSearchKeyboard.ts, packages/docs/src/lib/searchData.ts
Created new useSearchKeyboard hook to handle modal keyboard events (Escape, ArrowUp, ArrowDown, Enter) and API selection with navigation. Extracted ApiItem interface and large apiData dataset into dedicated searchData module for centralized search data management.
Android Purchase Helpers Consolidation
packages/google/openiap/src/horizon/java/dev/hyo/openiap/helpers/SharedHelpers.kt, packages/google/openiap/src/main/java/dev/hyo/openiap/helpers/CommonHelpers.kt, packages/google/openiap/src/play/java/dev/hyo/openiap/helpers/Helpers.kt
Moved shared purchase listener wrappers (onPurchaseUpdated, onPurchaseError), AndroidPurchaseArgs model, and purchase/error conversion logic (toAndroidPurchaseArgs(), toPurchaseError()) from Horizon and Play implementations into a new CommonHelpers module for cross-platform reuse.
Android ViewModel Removal
packages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapViewModel.kt
Deleted the OpenIapViewModel wrapper class that exposed store-backed StateFlow properties and coroutine-based public methods for connection, product fetching, purchase restoration, and purchase requests.
GraphQL Schema Linting
packages/gql/codegen/core/schema-linter.ts, packages/gql/codegen/index.ts
Introduced schema convention linter to validate GraphQL SDL files for platform-specific type naming (iOS/Android suffixes), union markers, and future field markers. Integrated linting into code generator with fail-fast error handling and re-exported linting functions and types from main index.
GraphQL Plugin Documentation
packages/gql/codegen/plugins/template-plugin.ts
Added deprecation notice to template plugin documentation indicating the plugin is not registered in the code generation pipeline and directing users to extend CodegenPlugin directly or register it in codegen/index.ts.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~23 minutes

Possibly related PRs

Suggested labels

🤖 android, 📖 documentation

Poem

🐰 A hop through the search, with keyboards so neat,
Helpers consolidated, from Android to fleet,
Schemas now linted, with GraphQL care,
Modal signals dance, in the docs-building air!

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 35.71% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The PR title is broad and covers multiple distinct changes across three packages (docs, google, gql), making it difficult to understand the primary focus at a glance. Consider a more specific title highlighting the main change, such as 'refactor: extract SearchModal logic and consolidate shared helpers' or focus on the most impactful refactoring.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/codebase-improvements

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

hyochan and others added 2 commits March 21, 2026 01:46
- Inline keyboard logic back into SearchModal (remove useSearchKeyboard hook)
- Revert signals.ts to simple boolean pattern (no object wrapper)
- Revert App.tsx signal usage accordingly
- Delete empty SharedHelpers.kt (Horizon flavor, comments-only file)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Run prettier --write on 36 files to pass CI format check.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (5)
packages/gql/codegen/core/schema-linter.ts (2)

88-93: Consider a more precise union marker pattern.

The current check trimmed.toLowerCase().includes('=> union') could match unintended comments like # This is not => union related. A regex anchored closer to expected marker format would reduce false positives.

♻️ Suggested refinement
       // Track union marker
-      if (trimmed.startsWith('#') && trimmed.toLowerCase().includes('=> union')) {
+      if (/^#\s*=>\s*union\b/i.test(trimmed)) {
         pendingUnionMarker = true;
         pendingUnionLine = lineNum;
         continue;
       }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/gql/codegen/core/schema-linter.ts` around lines 88 - 93, Replace the
loose includes check for union markers with a stricter anchored regex: instead
of using trimmed.toLowerCase().includes('=> union'), test trimmed against a
pattern that matches a comment-start followed by optional space and the literal
"=> union" (e.g. /^#\s*=>\s*union\b/i) so that only intended markers set
pendingUnionMarker and pendingUnionLine (the block using
trimmed.startsWith('#'), pendingUnionMarker, pendingUnionLine).

36-41: Use path.basename() for cross-platform path handling.

filePath.split('/').pop() assumes Unix-style paths. While Node.js often normalizes paths, using path.basename() is more robust and idiomatic.

♻️ Suggested fix
+import { basename } from 'node:path';
 import type { ParsedSchema } from './parser.js';
   for (const [filePath, sdl] of parsedSchema.sdlContents.entries()) {
-    const fileName = filePath.split('/').pop() ?? filePath;
+    const fileName = basename(filePath);
     const lines = sdl.split(/\r?\n/);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/gql/codegen/core/schema-linter.ts` around lines 36 - 41, The code
currently derives fileName via filePath.split('/').pop(), which is
platform-unsafe; update the loop that iterates parsedSchema.sdlContents to use
path.basename(filePath) to compute fileName (replace the
filePath.split('/').pop() usage) and ensure the Node.js path module is imported
(add import path from 'path' or const path = require('path') depending on module
style) so isIOSFile/isAndroidFile logic continues to work correctly across
platforms.
packages/google/openiap/src/horizon/java/dev/hyo/openiap/helpers/SharedHelpers.kt (1)

3-8: Consider deleting this empty placeholder file.

After the move it no longer contributes symbols, but the filename still implies shared helper logic lives here. Keeping the breadcrumb in Helpers.kt or package docs will age better than an always-empty source file.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/google/openiap/src/horizon/java/dev/hyo/openiap/helpers/SharedHelpers.kt`
around lines 3 - 8, Delete the empty placeholder file SharedHelpers.kt because
all helpers were moved to main/helpers/CommonHelpers.kt and Horizon-specific
utilities live in Helpers.kt; remove SharedHelpers.kt to avoid misleading
filename and leave any explanatory note in Helpers.kt or package-level
documentation instead.
packages/google/openiap/src/main/java/dev/hyo/openiap/helpers/CommonHelpers.kt (1)

57-70: Please pin this mapper down with regression tests.

This is now the shared normalization path for both flavors, and the follow-up developerBillingOption fix in this PR shows how easy it is to miss one field during future schema changes. A small purchase/subscription matrix test around the google → deprecated android fallback and copied fields would pay for itself quickly.

Also applies to: 76-122

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/google/openiap/src/main/java/dev/hyo/openiap/helpers/CommonHelpers.kt`
around lines 57 - 70, Add regression tests that lock down the
AndroidPurchaseArgs normalization/mapper logic by exercising the shared
normalization path for both "google" and deprecated "android" flavors: write
unit tests that cover a matrix of product types (one-time purchase vs
subscription), subscription offers vs subscriptionProductReplacementParams,
presence/absence of developerBillingOption, useAlternativeBilling and
obfuscatedAccountId/obfuscatedProfileId/offerToken/purchaseToken/replacementMode
fields, and verify fields are correctly copied/fallbacked from google to
AndroidPurchaseArgs; target the mapper/normalizer code that produces
AndroidPurchaseArgs (use the class name AndroidPurchaseArgs and any mapper
function used in the same module) so future schema changes will fail tests if a
field is missed.
packages/docs/src/hooks/useSearchKeyboard.ts (1)

13-19: Document the exported hook contract.

This hook now owns global listeners and navigation side effects, so a short JSDoc block at the export site would make that behavior much easier to discover.

📝 Suggested doc block
+/**
+ * Registers keyboard shortcuts for the docs search modal and returns the
+ * shared result-selection handler.
+ */
 export function useSearchKeyboard({

As per coding guidelines "Add JSDoc comments for public functions and exported APIs".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/docs/src/hooks/useSearchKeyboard.ts` around lines 13 - 19, Add a
concise JSDoc block above the exported function useSearchKeyboard (and
referencing UseSearchKeyboardProps) that documents the hook's public contract:
list the parameters, describe that it registers global keyboard listeners and
causes navigation/selection side effects (including which callbacks it invokes:
setSelectedIndex and onClose), and note lifecycle behavior such as cleanup on
unmount and when isOpen controls listener registration; keep it short and
actionable so callers know it creates global listeners and performs navigation
side effects that must be considered.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/docs/src/components/SearchModal.tsx`:
- Around line 12-24: The highlightMatch function is constructing a RegExp from
raw user input (received via handleSearchChange/searchQuery) which breaks on
regex metacharacters; fix it by escaping the query before building the RegExp
(add an escapeRegExp helper that replaces /[.*+?^${}()|[\]\\]/g with an escaped
version) and use the escaped string in new RegExp(`(${escapedQuery})`, 'gi')
inside highlightMatch so user input is treated as a literal string.

In `@packages/docs/src/lib/searchData.ts`:
- Around line 196-202: The dataset contains duplicate ApiItem.id values
("subscription-status-ios") which breaks React keys; find the duplicate entries
in searchData.ts (the ApiItem objects with title 'subscriptionStatusIOS' /
category 'iOS APIs') and make their id values unique (e.g., append a
distinguishing suffix like "-storekit2" or "-v2") and update any references that
consume api.id (e.g., the key used in SearchModal.tsx) so keys match the changed
ids; ensure every ApiItem.id across the dataset is globally unique to avoid
duplicate-key reconciliation issues.

In `@packages/docs/src/lib/signals.ts`:
- Around line 4-13: searchModalSignal was changed to hold an object { isOpen:
boolean } but callers still treat searchModalSignal.value as a boolean; update
the consumer in App.tsx to read and set the nested isOpen property instead of
the whole object: use searchModalSignal.value.isOpen when initializing state
(e.g., const [isSearchOpen, setIsSearchOpen] =
useState(searchModalSignal.value.isOpen)) and when updating state
(setIsSearchOpen(searchModalSignal.value.isOpen)); ensure any other usages that
pass searchModalSignal.value to the SearchModal prop or setState likewise access
.isOpen, and keep the existing openSearchModal and closeSearchModal helpers
(searchModalSignal, openSearchModal, closeSearchModal) unchanged unless you want
to revert the signal shape.

In
`@packages/google/openiap/src/main/java/dev/hyo/openiap/helpers/CommonHelpers.kt`:
- Around line 82-95: The code is copying the externally supplied `type` into
AndroidPurchaseArgs, creating two sources of truth; change the constructor calls
that set `type = type` (the AndroidPurchaseArgs instantiation at lines ~82-95
and the similar block at ~106-119) to derive the type from the branch context
instead of the external variable — e.g., set `type = ProductQueryType.INAPP` in
the purchase branch and `type = ProductQueryType.SUBS` in the subscription
branch, or alternatively add a `require(type == expectedType)` before returning
to assert they match; update only the AndroidPurchaseArgs constructions (and any
similar helper calls) to use the branch-determined value or validate equality.

---

Nitpick comments:
In `@packages/docs/src/hooks/useSearchKeyboard.ts`:
- Around line 13-19: Add a concise JSDoc block above the exported function
useSearchKeyboard (and referencing UseSearchKeyboardProps) that documents the
hook's public contract: list the parameters, describe that it registers global
keyboard listeners and causes navigation/selection side effects (including which
callbacks it invokes: setSelectedIndex and onClose), and note lifecycle behavior
such as cleanup on unmount and when isOpen controls listener registration; keep
it short and actionable so callers know it creates global listeners and performs
navigation side effects that must be considered.

In
`@packages/google/openiap/src/horizon/java/dev/hyo/openiap/helpers/SharedHelpers.kt`:
- Around line 3-8: Delete the empty placeholder file SharedHelpers.kt because
all helpers were moved to main/helpers/CommonHelpers.kt and Horizon-specific
utilities live in Helpers.kt; remove SharedHelpers.kt to avoid misleading
filename and leave any explanatory note in Helpers.kt or package-level
documentation instead.

In
`@packages/google/openiap/src/main/java/dev/hyo/openiap/helpers/CommonHelpers.kt`:
- Around line 57-70: Add regression tests that lock down the AndroidPurchaseArgs
normalization/mapper logic by exercising the shared normalization path for both
"google" and deprecated "android" flavors: write unit tests that cover a matrix
of product types (one-time purchase vs subscription), subscription offers vs
subscriptionProductReplacementParams, presence/absence of
developerBillingOption, useAlternativeBilling and
obfuscatedAccountId/obfuscatedProfileId/offerToken/purchaseToken/replacementMode
fields, and verify fields are correctly copied/fallbacked from google to
AndroidPurchaseArgs; target the mapper/normalizer code that produces
AndroidPurchaseArgs (use the class name AndroidPurchaseArgs and any mapper
function used in the same module) so future schema changes will fail tests if a
field is missed.

In `@packages/gql/codegen/core/schema-linter.ts`:
- Around line 88-93: Replace the loose includes check for union markers with a
stricter anchored regex: instead of using trimmed.toLowerCase().includes('=>
union'), test trimmed against a pattern that matches a comment-start followed by
optional space and the literal "=> union" (e.g. /^#\s*=>\s*union\b/i) so that
only intended markers set pendingUnionMarker and pendingUnionLine (the block
using trimmed.startsWith('#'), pendingUnionMarker, pendingUnionLine).
- Around line 36-41: The code currently derives fileName via
filePath.split('/').pop(), which is platform-unsafe; update the loop that
iterates parsedSchema.sdlContents to use path.basename(filePath) to compute
fileName (replace the filePath.split('/').pop() usage) and ensure the Node.js
path module is imported (add import path from 'path' or const path =
require('path') depending on module style) so isIOSFile/isAndroidFile logic
continues to work correctly across platforms.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4badd76f-25ab-4c98-867f-f66c7ce1cd82

📥 Commits

Reviewing files that changed from the base of the PR and between 312822c and 3e1e05f.

📒 Files selected for processing (14)
  • .github/workflows/ci.yml
  • packages/docs/src/App.tsx
  • packages/docs/src/components/SearchModal.tsx
  • packages/docs/src/hooks/useSearchKeyboard.ts
  • packages/docs/src/lib/searchData.ts
  • packages/docs/src/lib/signals.ts
  • packages/google/openiap/src/horizon/java/dev/hyo/openiap/helpers/SharedHelpers.kt
  • packages/google/openiap/src/main/java/dev/hyo/openiap/OpenIapViewModel.kt
  • packages/google/openiap/src/main/java/dev/hyo/openiap/helpers/CommonHelpers.kt
  • packages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapViewModel.kt
  • packages/google/openiap/src/play/java/dev/hyo/openiap/helpers/Helpers.kt
  • packages/gql/codegen/core/schema-linter.ts
  • packages/gql/codegen/index.ts
  • packages/gql/codegen/plugins/template-plugin.ts
💤 Files with no reviewable changes (1)
  • packages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapViewModel.kt

Comment on lines +82 to +95
AndroidPurchaseArgs(
skus = params.skus,
isOfferPersonalized = params.isOfferPersonalized,
obfuscatedAccountId = params.obfuscatedAccountId,
obfuscatedProfileId = params.obfuscatedProfileId,
offerToken = params.offerToken,
purchaseToken = null,
replacementMode = null,
subscriptionOffers = null,
subscriptionProductReplacementParams = null,
developerBillingOption = params.developerBillingOption,
type = type,
useAlternativeBilling = useAlternativeBilling
)
Copy link
Contributor

@coderabbitai coderabbitai bot Mar 20, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don’t keep two sources of truth for the product type.

Both branches already know whether they are normalizing a purchase or a subscription, but they still copy the externally supplied type unchanged. If those ever drift, this helper can build one branch’s args with the other branch’s ProductQueryType, which can send the purchase flow down the wrong billing path. Please derive type from the branch or require(...) that it matches before returning.

Also applies to: 106-119

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/google/openiap/src/main/java/dev/hyo/openiap/helpers/CommonHelpers.kt`
around lines 82 - 95, The code is copying the externally supplied `type` into
AndroidPurchaseArgs, creating two sources of truth; change the constructor calls
that set `type = type` (the AndroidPurchaseArgs instantiation at lines ~82-95
and the similar block at ~106-119) to derive the type from the branch context
instead of the external variable — e.g., set `type = ProductQueryType.INAPP` in
the purchase branch and `type = ProductQueryType.SUBS` in the subscription
branch, or alternatively add a `require(type == expectedType)` before returning
to assert they match; update only the AndroidPurchaseArgs constructions (and any
similar helper calls) to use the branch-determined value or validate equality.

Copy link
Member Author

Choose a reason for hiding this comment

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

This is the same pattern as the original code before the refactoring — type is an external field on RequestPurchaseProps that can be InApp, Subs, or All. The branch (Purchase vs Subscription) determines the request shape, not the product query type. For example, ProductQueryType.All is valid for both branches. Hardcoding the type per branch would break the existing API contract.

No change needed here.

Copy link
Contributor

Choose a reason for hiding this comment

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

@hyochan, understood — thanks for the clarification. type on RequestPurchaseProps represents the product query type (InApp, Subs, All) and is orthogonal to the request shape branch (Purchase vs Subscription). Since ProductQueryType.All is a legitimate value for either branch, passing it through directly is the correct pattern. My concern was unfounded. No change needed.

( •ᴥ• ) ʕっ•ᴥ•ʔっ


✏️ Learnings added
Learnt from: hyochan
Repo: hyodotdev/openiap PR: 82
File: packages/google/openiap/src/main/java/dev/hyo/openiap/helpers/CommonHelpers.kt:82-95
Timestamp: 2026-03-20T17:02:34.907Z
Learning: In `packages/google/openiap/src/main/java/dev/hyo/openiap/helpers/CommonHelpers.kt` (Kotlin, Android), the `type` field on `RequestPurchaseProps` (`ProductQueryType.InApp`, `ProductQueryType.Subs`, or `ProductQueryType.All`) is independent of the `when` branch (`Request.Purchase` vs `Request.Subscription`). The branch determines the request payload shape, not the product query type. `ProductQueryType.All` is valid for both branches, so `type` must be passed through from the external field rather than hardcoded per branch. Hardcoding it would break the existing API contract.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: hyochan
Repo: hyodotdev/openiap PR: 67
File: packages/apple/Sources/Helpers/StoreKitTypesBridge.swift:449-466
Timestamp: 2026-01-18T15:32:05.480Z
Learning: Treat introductoryOfferEligibility as a server-signed JWS string across all platforms. Currently defined as Boolean in packages/apple/Sources/Helpers/StoreKitTypesBridge.swift and in the GraphQL schema, but StoreKit API Product.PurchaseOption.introductoryOfferEligibility(compactJWS:) requires a JWS string. This is a breaking change that requires updates across all platform bindings (TypeScript, Swift, Kotlin, Dart, GDScript) and the GraphQL schema. Plan a coordinated migration: change the type from Boolean to String (JWS), update all schema and client bindings, add validation/migration tests, and document the upgrade path for downstream consumers.

hyochan and others added 2 commits March 21, 2026 01:54
CI checks src/**/*.{ts,tsx,css} but previous commit only formatted
ts/tsx files, missing the CSS glob.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add escapeRegExp helper to prevent crash when typing regex
  metacharacters (e.g., (, [, +) in the search modal
- Fix duplicate 'subscription-status-ios' id in searchData.ts
  (renamed to 'subscription-status-ios-type' for the Types entry)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@hyochan hyochan merged commit bb3358a into main Mar 20, 2026
7 checks passed
@hyochan hyochan deleted the refactor/codebase-improvements branch March 20, 2026 17:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant