Skip to content

Fixed lazy URL service handing out URLs for non-public resources - #28890

Merged
allouis merged 5 commits into
mainfrom
lazy-url-non-public-resources
Jun 29, 2026
Merged

Fixed lazy URL service handing out URLs for non-public resources#28890
allouis merged 5 commits into
mainfrom
lazy-url-non-public-resources

Conversation

@allouis

@allouis allouis commented Jun 25, 2026

Copy link
Copy Markdown
Collaborator

Summary

While the lazy URL service runs alongside eager in compare mode, the lazy forward path handed out URLs for resources eager returns /404/ for — most visibly internal/private tags (the reported divergence), and also draft/scheduled/sent posts.

Root cause

Eager only builds URLs for resources that pass each type's resource-fetch filter (visibility:public for tags, status:published+type:* for posts/pages), applied at fetch time. The lazy forward path (getUrlForResource/ownsResource) only consulted the per-router filter, which is null for taxonomy routers, so any tag/post matched.

Changes (5 atomic commits)

  1. Fixed lazy URL service handing out URLs for non-public resources — lazy applies the same per-type base filter (BASE_FILTERS, an explicit inline constant duplicated from services/url/config.js for now — lazy is meant to replace eager and consolidate) in getUrlForResource/ownsResource. A resource that reaches URL generation must carry the columns its base filter reads (status/visibility); production callers always do (full models, or forced by the serializers), so a thin resource throws LAZY_URL_THIN_RESOURCE rather than silently 404 a URL eager would produce. Authors have no base filter (users.visibility is schema-pinned to 'public'). The url-service-parity integration test fed eager's cached resources (which drop those columns after fetch-time filtering) — updated it to re-add them so it exercises the shape lazy actually receives.
  2. Added getRequiredFields to the lazy URL service and facade — exposes the per-type columns the lazy backend needs (mirrors getRequiredRelations); returns [] under eager.
  3. Forced lazy URL base-filter columns in the API input serializers — under ?fields=url the selected columns are narrowed, so posts/pages/tags serializers force the base-filter columns back into the fetch. No-op under eager.
  4. Forced permalink columns for lazy URL generation under ?fields=url — lazy builds URLs by substituting resource fields into the permalink template (slug, id, published_at for date tokens), which ?fields=url also strips. getRequiredFields reports each type's permalink scalar columns; permalink relations stay covered by getRequiredRelations.
  5. Required router-filter scalar columns on the lazy forward path — a router filter's own-columns (e.g. featured:true) were neither reported by getRequiredFields nor required by the thin-check, so a thin record under ?fields=url could route a featured post to /:slug/ instead of /featured/:slug/. Now the non-relation scalar fields a router filter references are reported (so the serializers force them) and required on the forward path (relations and the page/type discriminator stay excluded).

The eager services/url/config.js is not modified.

Testing (run locally)

  • Unit: test/unit/api + test/unit/server + test/unit/frontend — all green.
  • Integration: full test/integration suite green (incl. url-service-parity.test.js).

Known remaining divergence (follow-up, not in this PR)

  • shouldHavePosts: eager omits tags/authors with zero published posts from its forward map → /404/; lazy's forward path still returns a URL (the reverse resolveUrl path already matches, via TagPublic). Needs post-count data on the resource. Low impact: nothing branches on a forward tag/author /404/, and the archive page itself still 404s via the reverse path.

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b8e1e2cb-f35a-4991-bf48-045609c91bde

📥 Commits

Reviewing files that changed from the base of the PR and between b84711e and 8b66e12.

📒 Files selected for processing (11)
  • ghost/core/core/server/api/endpoints/utils/serializers/input/authors.js
  • ghost/core/core/server/api/endpoints/utils/serializers/input/pages.js
  • ghost/core/core/server/api/endpoints/utils/serializers/input/posts.js
  • ghost/core/core/server/api/endpoints/utils/serializers/input/tags.js
  • ghost/core/core/server/api/endpoints/utils/serializers/input/utils/url.js
  • ghost/core/core/server/services/url/lazy-url-service.ts
  • ghost/core/core/server/services/url/url-service-facade.ts
  • ghost/core/test/integration/url-service-parity.test.js
  • ghost/core/test/unit/api/canary/utils/serializers/input/utils/url.test.js
  • ghost/core/test/unit/server/services/url/lazy-url-service.test.js
  • ghost/core/test/unit/server/services/url/url-service-facade.test.js
🚧 Files skipped from review as they are similar to previous changes (10)
  • ghost/core/core/server/api/endpoints/utils/serializers/input/tags.js
  • ghost/core/core/server/services/url/url-service-facade.ts
  • ghost/core/test/unit/server/services/url/url-service-facade.test.js
  • ghost/core/core/server/api/endpoints/utils/serializers/input/authors.js
  • ghost/core/core/server/api/endpoints/utils/serializers/input/pages.js
  • ghost/core/test/integration/url-service-parity.test.js
  • ghost/core/core/server/api/endpoints/utils/serializers/input/posts.js
  • ghost/core/test/unit/api/canary/utils/serializers/input/utils/url.test.js
  • ghost/core/core/server/api/endpoints/utils/serializers/input/utils/url.js
  • ghost/core/test/unit/server/services/url/lazy-url-service.test.js

Walkthrough

Serializer inputs for authors, pages, posts, and tags now request lazy URL columns through a shared helper. UrlServiceFacade exposes required-field lookup, and LazyUrlService now computes required fields, applies base filters, and enforces thin-resource checks in URL and ownership resolution. Tests cover the helper, facade delegation, lazy service behavior, and parity resource shaping.

Possibly related PRs

  • TryGhost/Ghost#28891: Shares the same lazy URL resolution area and also changes how required URL-related fields are supplied for lazy URL handling.

Suggested reviewers

  • vershwal
  • rob-ghost
  • jonatansberg
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: lazy URL generation now respects non-public resources.
Description check ✅ Passed The description is detailed and directly matches the changes made to the lazy URL service and serializers.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch lazy-url-non-public-resources

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

@nx-cloud

nx-cloud Bot commented Jun 25, 2026

Copy link
Copy Markdown

🤖 Nx Cloud AI Fix

Ensure the fix-ci command is configured to always run in your CI pipeline to get automatic fixes in future runs. For more information, please see https://nx.dev/ci/features/self-healing-ci


View your CI Pipeline Execution ↗ for commit 8b66e12

Command Status Duration Result
nx run ghost:test:ci:integration ✅ Succeeded 2m 35s View ↗
nx run ghost:test:integration ✅ Succeeded 2m 41s View ↗
nx run ghost:test:legacy ✅ Succeeded 2m 42s View ↗
nx run ghost:test:e2e ✅ Succeeded 2m 27s View ↗
nx run-many --target=build --projects=tag:publi... ✅ Succeeded 3s View ↗
nx run-many -t lint -p ghost ✅ Succeeded 34s View ↗
nx run-many -t test:unit -p ghost ✅ Succeeded 29s View ↗
nx run @tryghost/admin:build ✅ Succeeded 5s View ↗
Additional runs (2) ✅ Succeeded ... View ↗

💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗


☁️ Nx Cloud last updated this comment at 2026-06-29 05:48:28 UTC

@github-actions

Copy link
Copy Markdown
Contributor

E2E Tests Failed

To view the Playwright test report locally, run:

REPORT_DIR=$(mktemp -d) && gh run download 28148235613 -n playwright-report -D "$REPORT_DIR" && npx playwright show-report "$REPORT_DIR"

@allouis
allouis force-pushed the lazy-url-non-public-resources branch from 0160c12 to 484df86 Compare June 25, 2026 05:31
@github-actions

Copy link
Copy Markdown
Contributor

E2E Tests Failed

To view the Playwright test report locally, run:

REPORT_DIR=$(mktemp -d) && gh run download 28149109638 -n playwright-report -D "$REPORT_DIR" && npx playwright show-report "$REPORT_DIR"

@allouis
allouis force-pushed the lazy-url-non-public-resources branch 4 times, most recently from b4d2de8 to 9ba39b3 Compare June 25, 2026 08:16
@allouis
allouis requested a review from vershwal June 25, 2026 08:37
@allouis
allouis marked this pull request as ready for review June 25, 2026 08:37

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ghost/core/core/server/services/url/lazy-url-service.ts`:
- Around line 153-173: getRequiredFields() currently only collects
permalink-derived fields and base filters, so router-specific scalar filter
fields like featured or page can be missing and cause lazy routing to
mis-evaluate. Update getRequiredFields() in lazy-url-service.ts to also inspect
each matching router config’s filter definitions and add any non-relation scalar
fields referenced by config.filter to the required field set, alongside
slug/id/published_at, so thin resources still include the values needed by
filterMatches() on the forward path.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c99cc864-7856-4ede-89ca-0075d004af53

📥 Commits

Reviewing files that changed from the base of the PR and between 0888b43 and 9ba39b3.

📒 Files selected for processing (11)
  • ghost/core/core/server/api/endpoints/utils/serializers/input/authors.js
  • ghost/core/core/server/api/endpoints/utils/serializers/input/pages.js
  • ghost/core/core/server/api/endpoints/utils/serializers/input/posts.js
  • ghost/core/core/server/api/endpoints/utils/serializers/input/tags.js
  • ghost/core/core/server/api/endpoints/utils/serializers/input/utils/url.js
  • ghost/core/core/server/services/url/lazy-url-service.ts
  • ghost/core/core/server/services/url/url-service-facade.ts
  • ghost/core/test/integration/url-service-parity.test.js
  • ghost/core/test/unit/api/canary/utils/serializers/input/utils/url.test.js
  • ghost/core/test/unit/server/services/url/lazy-url-service.test.js
  • ghost/core/test/unit/server/services/url/url-service-facade.test.js

Comment thread ghost/core/core/server/services/url/lazy-url-service.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
ghost/core/core/server/services/url/lazy-url-service.ts (1)

253-263: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Apply thin-resource checks before ownership matching.

ownsResource() now gates on base filters, but it still evaluates router filters on whatever fields happen to be present. A thin record missing featured, tags, etc. can skip an earlier filtered router and incorrectly let a later catch-all router claim ownership.

Suggested fix
         // router owns it. Mirrors the base-filter gate in getUrlForResource.
         const record = this._recordForFilter(resource);
+        if (this._hasRouterForType(routerType)) {
+            this._assertBaseFieldsPresent(routerType, resource);
+        }
         if (!this._baseFilterMatches(routerType, record)) {
             return false;
         }
         // Ownership is exclusive: only the first matching router of the type
         // owns the resource, matching eager's reservation.
-        const owner = this.routerConfigs.find(
-            c => c.resourceType === routerType && filterMatches(c.compiledFilter, record)
-        );
+        const owner = this.routerConfigs.find((c) => {
+            if (c.resourceType !== routerType) {
+                return false;
+            }
+            this._assertNotThin(c, resource, routerType);
+            return filterMatches(c.compiledFilter, record);
+        });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ghost/core/core/server/services/url/lazy-url-service.ts` around lines 253 -
263, Update ownsResource() in lazy-url-service.ts so thin records are rejected
before router ownership matching: after _recordForFilter(resource) and the base
filter check, add the same thin-resource validation used elsewhere before
calling routerConfigs.find/filterMatches. This should ensure missing fields like
featured or tags do not let a later catch-all router claim ownership. Reference
ownsResource(), _recordForFilter(), _baseFilterMatches(), and
routerConfigs.find() when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ghost/core/core/server/services/url/lazy-url-service.ts`:
- Around line 76-83: The field extraction logic in lazyUrlService’s filter
parsing is too broad because the regex scans the entire filter string and can
treat colon-containing values as fake fields. Update the extraction in the same
filter-handling path used by buildFilter to only match real filter-expression
field names, or otherwise anchor matches to expression boundaries before adding
to fields. Keep the existing FILTER_NON_SCALAR_FIELDS and _assertNotThin flow,
but ensure the matcher in the field-collection logic does not capture URLs,
timestamps, or other literal values.

---

Outside diff comments:
In `@ghost/core/core/server/services/url/lazy-url-service.ts`:
- Around line 253-263: Update ownsResource() in lazy-url-service.ts so thin
records are rejected before router ownership matching: after
_recordForFilter(resource) and the base filter check, add the same thin-resource
validation used elsewhere before calling routerConfigs.find/filterMatches. This
should ensure missing fields like featured or tags do not let a later catch-all
router claim ownership. Reference ownsResource(), _recordForFilter(),
_baseFilterMatches(), and routerConfigs.find() when making the change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 738b22d3-35ab-40c5-ac92-1fbc33a7c287

📥 Commits

Reviewing files that changed from the base of the PR and between 9ba39b3 and 0d86527.

📒 Files selected for processing (2)
  • ghost/core/core/server/services/url/lazy-url-service.ts
  • ghost/core/test/unit/server/services/url/lazy-url-service.test.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • ghost/core/test/unit/server/services/url/lazy-url-service.test.js

Comment thread ghost/core/core/server/services/url/lazy-url-service.ts Outdated
@allouis
allouis force-pushed the lazy-url-non-public-resources branch from 0d86527 to 44fd692 Compare June 25, 2026 12:03

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
ghost/core/core/server/services/url/lazy-url-service.ts (1)

257-264: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Apply the same thin-resource guard in ownership checks.

ownsResource() now evaluates the base filter, but unlike getUrlForResource() it does not call _assertBaseFieldsPresent() first. If a thin post/page/tag lacks status, type, or visibility, filterMatches() can return false and make a valid resource look unowned instead of raising LAZY_URL_THIN_RESOURCE.

🐛 Proposed fix
         // A resource failing its base filter is not in eager's map, so no
         // router owns it. Mirrors the base-filter gate in getUrlForResource.
         const record = this._recordForFilter(resource);
+        this._assertBaseFieldsPresent(routerType, resource);
         if (!this._baseFilterMatches(routerType, record)) {
             return false;
         }

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: fd6703fa-a871-4acc-bce7-e741548b3d79

📥 Commits

Reviewing files that changed from the base of the PR and between 0d86527 and 44fd692.

📒 Files selected for processing (2)
  • ghost/core/core/server/services/url/lazy-url-service.ts
  • ghost/core/test/unit/server/services/url/lazy-url-service.test.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • ghost/core/test/unit/server/services/url/lazy-url-service.test.js

@allouis
allouis force-pushed the lazy-url-non-public-resources branch from 44fd692 to b84711e Compare June 29, 2026 04:55

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ghost/core/core/server/services/url/lazy-url-service.ts`:
- Around line 257-267: `ownsResource()` in `lazy-url-service.ts` is missing the
thin-resource guard, so undefined fields can still fall through the
router/filter checks and be owned incorrectly. Add the same thin-resource
detection used elsewhere in the lazy URL flow before evaluating
`_baseFilterMatches()` or `filterMatches()`, and make `ownsResource()` raise
`LAZY_URL_THIN_RESOURCE` for incomplete records instead of returning
false/allowing fallback ownership. Use the existing `ownsResource`,
`_recordForFilter`, and `_baseFilterMatches` logic as the integration points so
the behavior matches the eager-path checks.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0dc89045-b363-4208-a9a7-a587a5705d0f

📥 Commits

Reviewing files that changed from the base of the PR and between 44fd692 and b84711e.

📒 Files selected for processing (11)
  • ghost/core/core/server/api/endpoints/utils/serializers/input/authors.js
  • ghost/core/core/server/api/endpoints/utils/serializers/input/pages.js
  • ghost/core/core/server/api/endpoints/utils/serializers/input/posts.js
  • ghost/core/core/server/api/endpoints/utils/serializers/input/tags.js
  • ghost/core/core/server/api/endpoints/utils/serializers/input/utils/url.js
  • ghost/core/core/server/services/url/lazy-url-service.ts
  • ghost/core/core/server/services/url/url-service-facade.ts
  • ghost/core/test/integration/url-service-parity.test.js
  • ghost/core/test/unit/api/canary/utils/serializers/input/utils/url.test.js
  • ghost/core/test/unit/server/services/url/lazy-url-service.test.js
  • ghost/core/test/unit/server/services/url/url-service-facade.test.js
🚧 Files skipped from review as they are similar to previous changes (10)
  • ghost/core/test/unit/server/services/url/url-service-facade.test.js
  • ghost/core/core/server/api/endpoints/utils/serializers/input/tags.js
  • ghost/core/core/server/services/url/url-service-facade.ts
  • ghost/core/core/server/api/endpoints/utils/serializers/input/posts.js
  • ghost/core/core/server/api/endpoints/utils/serializers/input/pages.js
  • ghost/core/test/unit/api/canary/utils/serializers/input/utils/url.test.js
  • ghost/core/core/server/api/endpoints/utils/serializers/input/authors.js
  • ghost/core/core/server/api/endpoints/utils/serializers/input/utils/url.js
  • ghost/core/test/integration/url-service-parity.test.js
  • ghost/core/test/unit/server/services/url/lazy-url-service.test.js

Comment thread ghost/core/core/server/services/url/lazy-url-service.ts
allouis added 5 commits June 29, 2026 05:36
ref https://linear.app/ghost/issue/HKG-1875

The lazy URL service runs alongside the eager service in compare mode, and the two diverged: lazy returned real URLs for internal tags and draft posts where eager returns /404/. Eager only builds URLs for resources that pass each type's resource-fetch filter — visibility:public for tags, status:published for posts and pages — applied once when it loads its cache. The lazy forward path only consulted the per-router filter, which is null for taxonomy routers, so any tag or post matched.

This applies the same per-type base filter before any router runs. The filters live in an inline BASE_FILTERS constant, deliberately duplicated from the eager config for now: lazy is meant to replace eager, at which point the two consolidate into one source. A resource that reaches URL generation must carry the columns its filter reads and production callers always provide them, so a resource missing them is a programmer error that throws rather than silently returning a URL eager would 404. Authors have no base filter, since users.visibility is schema-pinned to public. The parity integration test fed eager's cached resources, which drop those columns after filtering, so it now re-adds them to exercise the shape lazy actually receives.
ref https://linear.app/ghost/issue/HKG-1875

Now that lazy gates URL generation on per-type base filters, callers that serialize a resource's URL need a way to learn which columns those filters read so they can ensure the columns are loaded. This exposes that set through getRequiredFields on both the lazy service and the facade, mirroring the existing getRequiredRelations. The facade returns an empty array under the eager service, so eager callers are unaffected.
ref https://linear.app/ghost/issue/HKG-1875

A Content API ?fields=url query narrows the selected columns, so a resource can reach URL generation without the columns the lazy service needs to decide whether it is routable — status for posts and pages, visibility for tags. The posts, pages and tags input serializers now force those columns back into the fetch whenever url is requested; the response is still trimmed to the requested fields afterwards. This is what guarantees an internal tag or draft post still carries its visibility or status so the base filter applies. It is a no-op under the eager service, where getRequiredFields returns nothing.
ref https://linear.app/ghost/issue/HKG-1875

Unlike eager, which looks URLs up by id, lazy builds them by substituting the resource's own fields into the router permalink template, so it also needs the scalar columns a permalink references — slug, id, and published_at for date tokens like :year/:month/:day. A ?fields=url query strips these too, which left lazy producing broken URLs such as /tag//. getRequiredFields now reports each type's permalink columns alongside its base-filter columns, and the authors serializer gains the same column-forcing hook, since authors have no base filter but their /author/:slug/ permalink still needs slug. Permalink relations such as primary_tag and primary_author remain covered by getRequiredRelations.
ref https://linear.app/ghost/issue/HKG-1875

A filtered collection router such as featured:true also needs its filter's own columns on the resource to choose the same router eager would. These were neither reported by getRequiredFields nor required on the forward path, so under ?fields=url a thin record let filterMatches see an undefined field and a featured post routed to /:slug/ instead of /featured/:slug/. The non-relation scalar fields a router filter references are now reported so the serializers force them, and required on the forward path so a thin resource is rejected rather than silently mis-routed. Field names are matched only at NQL expression boundaries, so colon-bearing values such as URLs or timestamps are not mistaken for fields; relation fields and the page/type discriminator stay excluded.
@allouis
allouis force-pushed the lazy-url-non-public-resources branch from b84711e to 8b66e12 Compare June 29, 2026 05:38
@allouis
allouis merged commit 15d7bac into main Jun 29, 2026
43 checks passed
@allouis
allouis deleted the lazy-url-non-public-resources branch June 29, 2026 06:17
Sign up for free to 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.

2 participants