Skip to content

feat: media library backend with five browse lenses#542

Merged
electather merged 5 commits into
mainfrom
feat/library-backend
Jun 3, 2026
Merged

feat: media library backend with five browse lenses#542
electather merged 5 commits into
mainfrom
feat/library-backend

Conversation

@electather

Copy link
Copy Markdown
Owner

Summary

Builds the backend for the owned-media library and rewires the existing (mock-only) library page to it across five browse lenses — A–Z, Timeline, Server, Quality, and Collections — implementing docs/2026-06-02-library-backend-design.md end to end. A new library server module owns a denormalized library_items projection synced from collection@v1, served through the unified media-sources route plus dedicated facets/collections endpoints; the client swaps its fixtures for real data with infinite scroll while keeping the existing look.

Design document

Implements docs/2026-06-02-library-backend-design.md (updated in this PR with per-phase status, deviations, and a bug ledger — code and doc are in sync).

Type of change

  • New feature (non-breaking change that adds functionality)

Scope

  • @ent-mcp/client
  • @ent-mcp/server
  • @ent-mcp/shared

Also @ent-mcp/plugin-sdk and @ent-mcp/plugin-tmdb (collection membership).

What's here

  • Shared@ent-mcp/shared/library (lens/watched/quality enums, facet + collection types, query schemas); collection on the media item; collectionId/Name on CanonicalMetadata; CompactMediaItem.section for grouped lenses.
  • Franchise threading — TMDB belongs_to_collectionmapMovie (TV → null) → canonical metadata, persisted on insert and conflict-update.
  • Schemalibrary_items (composite (user_id, id) PK) + user_library_seed; migration 0004; server-mod-library / -internal / server-schema-library fallow zones.
  • Module — membership sync (tombstone, no-resurrect, no wipe on partial/empty feed), denormalized hydrate (catalog + availability + progress), the four lens sources wired into the unified REGISTRY, and /api/library/{facets,collections}. library.sync (6h) + library.hydrate (hourly) jobs.
  • Client — real data via the shared media-rows infinite query + /api/library/*, section headers on group-key change, id+section keys, virtualized infinite scroll, quality chips, facet-driven A–Z + decade rails. Mock fixtures and client-side grouping/filtering removed.

Test plan

  • vp check passes locally — 1553 files, no lint/type errors.
  • vp test passes locally — full monorepo 2674 tests; library suites green (server + client). fallow dead-code → 0 boundary violations, baseline unchanged.

New regression tests cover: sync diff/idempotency/no-resurrect/no-wipe-on-outage, franchise mapping + persist, lens keyset stability (incl. NULL-year and null-name boundaries), json_each server/quality fan-out, facet counts, hydrate, multi-user ownership, and the FE hooks/section grouping/infinite-scroll/collections/facets.

Adversarial review across phases caught and fixed 11 real bugs (incl. an outage wiping the owned library, a multi-tenancy PK collision, two keyset row-drop blockers, and a json_each runtime error) — full ledger in the design doc.

Known followup

Multi-value filter axes on the item lenses collapse to the first value because the unified /media/sources/:id resolver reads c.req.query() (single-value); collections + facets already honor multi-value. Server-side fix: read c.req.queries().

Checklist

  • PR title follows Conventional Commits
  • Tests added or updated for new behaviour
  • Docs updated (docs/2026-06-02-library-backend-design.md)
  • Changesets included (@ent-mcp/server, @ent-mcp/client, @ent-mcp/plugin-sdk, @ent-mcp/plugin-tmdb)
  • No secrets, credentials, or personal data committed

Omid Astaraki added 5 commits June 2, 2026 22:45
…tems

Add the @ent-mcp/shared/library subpath (lens/watched/quality enums, facet
and collection types, lens/collections query schemas), an additive optional
collection field on the plugin-sdk media item, collection membership on the
shared CanonicalMetadata type, CompactMediaItem.section for grouped lenses,
and the library lens source ids. Includes the library backend design doc.
Map a movie's belongs_to_collection into the canonical metadata: tmdb mapMovie
emits a stringified collection (TV maps to null), the catalog mapper persists
collectionId/collectionName, and writeMetadata keeps them on a conflict update
so a re-fetch refreshes franchise data. Adds the two canonical_metadata columns.
Add the library-owned library_items (composite (user_id, id) primary key so the
same title can be owned by many users) and user_library_seed tables, the 0004
migration (also adding the canonical_metadata collection columns), and the
server-mod-library / -internal / server-schema-library fallow zones plus the
client-feat-library allows for the shared media and virtualized grid.
…ets, collections

New library module: membership sync from collection@v1 (tombstone, no-resurrect,
no wipe on a partial/empty feed), denormalized hydrate from catalog + availability
+ progress, the az/timeline/server/quality lens sources wired into the unified
media REGISTRY, and the /api/library facets + collections endpoints. Adds
MediaService.getCollectionFeed and getAvailabilityQuality. Registers the
library.sync (6h) and library.hydrate (hourly) jobs. Tests cover sync diff,
keyset stability, json_each fan-out, facets, hydrate, and multi-user ownership.
Replace the mock library data layer with the real endpoints (look preserved):
the four item lenses reuse the shared media-rows infinite query against
/api/media/sources/library-<lens>, collections and facets via /api/library.
Section headers insert on group-key change over the flat stream (A-Z letter,
timeline decade, server/quality section), rows key by id+section, infinite
scroll via the shared virtual grid. Quality chips render from item tags; A-Z and
decade rails are facet-driven. Drops the fixtures, fetch-all stub, and
client-side grouping/filtering.
@github-actions github-actions Bot added the status: ready-for-review Ready for reviewer attention label Jun 2, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot 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: 91cd76f11f

ℹ️ 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 thread apps/server/src/library/repo/hydrate.ts
Comment thread apps/server/src/library/internal/reads.ts
@github-actions

github-actions Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Fallow combined report

Found 69 findings.

Duplication (52, showing 50)
Severity Rule Location Description
minor fallow/code-duplication apps/client/src/features/library/lib/types.ts:50 Code clone group 3 (24 lines, 2 instances)
minor fallow/code-duplication apps/server/src/library/errors.ts:2 Code clone group 11 (18 lines, 3 instances)
minor fallow/code-duplication apps/server/src/library/errors.ts:2 Code clone group 10 (20 lines, 2 instances)
minor fallow/code-duplication apps/server/src/library/internal/media-sources.ts:28 Code clone group 12 (38 lines, 2 instances)
minor fallow/code-duplication apps/server/src/library/internal/media-sources.ts:65 Code clone group 12 (38 lines, 2 instances)
minor fallow/code-duplication apps/server/src/library/internal/media-sources.ts:110 Code clone group 7 (6 lines, 3 instances)
minor fallow/code-duplication apps/server/src/library/internal/media-sources.ts:110 Code clone group 13 (7 lines, 2 instances)
minor fallow/code-duplication apps/server/src/library/internal/media-sources.ts:111 Code clone group 9 (5 lines, 4 instances)
minor fallow/code-duplication apps/server/src/library/internal/media-sources.ts:111 Code clone group 6 (5 lines, 3 instances)
minor fallow/code-duplication apps/server/src/library/internal/media-sources.ts:111 Code clone group 8 (8 lines, 3 instances)
minor fallow/code-duplication apps/server/src/library/jobs/hydrate-library.ts:16 Code clone group 14 (36 lines, 2 instances)
minor fallow/code-duplication apps/server/src/library/jobs/sync-library.ts:8 Code clone group 15 (32 lines, 2 instances)
minor fallow/code-duplication apps/server/src/library/repo/collections.ts:89 Code clone group 16 (9 lines, 3 instances)
minor fallow/code-duplication apps/server/src/library/repo/collections.ts:177 Code clone group 17 (10 lines, 2 instances)
minor fallow/code-duplication apps/server/src/library/repo/collections.ts:183 Code clone group 17 (10 lines, 2 instances)
minor fallow/code-duplication apps/server/src/library/repo/collections.ts:199 Code clone group 18 (24 lines, 2 instances)
minor fallow/code-duplication apps/server/src/library/repo/collections.ts:245 Code clone group 19 (24 lines, 2 instances)
minor fallow/code-duplication apps/server/src/library/repo/collections.ts:265 Code clone group 20 (10 lines, 2 instances)
minor fallow/code-duplication apps/server/src/library/repo/facets.ts:68 Code clone group 21 (12 lines, 2 instances)
minor fallow/code-duplication apps/server/src/library/repo/facets.ts:87 Code clone group 21 (12 lines, 2 instances)
minor fallow/code-duplication apps/server/src/library/repo/facets.ts:89 Code clone group 22 (18 lines, 2 instances)
minor fallow/code-duplication apps/server/src/library/repo/facets.ts:114 Code clone group 22 (18 lines, 2 instances)
minor fallow/code-duplication apps/server/src/library/repo/lens-pages.ts:108 Code clone group 23 (19 lines, 2 instances)
minor fallow/code-duplication apps/server/src/library/repo/lens-pages.ts:108 Code clone group 16 (9 lines, 3 instances)
minor fallow/code-duplication apps/server/src/library/repo/lens-pages.ts:132 Code clone group 23 (19 lines, 2 instances)
minor fallow/code-duplication apps/server/src/library/repo/lens-pages.ts:132 Code clone group 16 (9 lines, 3 instances)
minor fallow/code-duplication apps/server/src/library/repo/lens-pages.ts:164 Code clone group 24 (6 lines, 2 instances)
minor fallow/code-duplication apps/server/src/library/repo/lens-pages.ts:170 Code clone group 24 (6 lines, 2 instances)
minor fallow/code-duplication apps/server/src/library/repo/lens-pages.ts:177 Code clone group 18 (24 lines, 2 instances)
minor fallow/code-duplication apps/server/src/library/repo/lens-pages.ts:252 Code clone group 20 (10 lines, 2 instances)
minor fallow/code-duplication apps/server/src/library/repo/lens-pages.ts:325 Code clone group 25 (6 lines, 2 instances)
minor fallow/code-duplication apps/server/src/library/repo/lens-pages.ts:363 Code clone group 25 (6 lines, 2 instances)
minor fallow/code-duplication apps/server/src/library/repo/lens-pages.ts:441 Code clone group 19 (24 lines, 2 instances)
minor fallow/code-duplication apps/server/src/library/repo/seed.ts:10 Code clone group 26 (16 lines, 2 instances)
minor fallow/code-duplication apps/server/src/library/service.ts:210 Code clone group 13 (7 lines, 2 instances)
minor fallow/code-duplication apps/server/src/library/service.ts:210 Code clone group 7 (6 lines, 3 instances)
minor fallow/code-duplication apps/server/src/library/service.ts:211 Code clone group 8 (8 lines, 3 instances)
minor fallow/code-duplication apps/server/src/library/service.ts:211 Code clone group 9 (5 lines, 4 instances)
minor fallow/code-duplication apps/server/src/library/service.ts:211 Code clone group 6 (5 lines, 3 instances)
minor fallow/code-duplication apps/server/src/library/sources/az.ts:12 Code clone group 27 (42 lines, 4 instances)
minor fallow/code-duplication apps/server/src/library/sources/keyset.ts:35 Code clone group 28 (15 lines, 2 instances)
minor fallow/code-duplication apps/server/src/library/sources/keyset.ts:84 Code clone group 28 (15 lines, 2 instances)
minor fallow/code-duplication apps/server/src/library/sources/keyset.ts:107 Code clone group 29 (5 lines, 2 instances)
minor fallow/code-duplication apps/server/src/library/sources/quality.ts:12 Code clone group 27 (42 lines, 4 instances)
minor fallow/code-duplication apps/server/src/library/sources/server.ts:12 Code clone group 27 (42 lines, 4 instances)
minor fallow/code-duplication apps/server/src/library/sources/timeline.ts:12 Code clone group 27 (42 lines, 4 instances)
minor fallow/code-duplication apps/server/src/library/types.ts:42 Code clone group 30 (36 lines, 2 instances)
minor fallow/code-duplication apps/server/src/media/service/index.ts:690 Code clone group 32 (29 lines, 3 instances)
minor fallow/code-duplication apps/server/src/media/service/index.ts:830 Code clone group 33 (7 lines, 2 instances)
minor fallow/code-duplication apps/server/src/media/service/index.ts:861 Code clone group 34 (18 lines, 2 instances)

Showing 50 of 52 findings. Run fallow locally or inspect the CI output for the full report.

Health (16)
Severity Rule Location Description
minor fallow/high-crap-score apps/client/src/features/library/lib/fetchers.ts:38 'filtersToQuery' has CRAP score 42.0 (threshold: 30.0, cyclomatic 6)
critical fallow/high-crap-score apps/client/src/features/library/lib/section-groups.ts:53 'sectionOf' has CRAP score 110.0 (threshold: 30.0, cyclomatic 10)
major fallow/high-crap-score apps/client/src/features/library/lib/types.ts:55 'constructor' has CRAP score 56.0 (threshold: 30.0, cyclomatic 7)
minor fallow/high-crap-score apps/server/src/library/internal/enrich.ts:68 'toCompactItem' has CRAP score 42.0 (threshold: 30.0, cyclomatic 6)
critical fallow/high-crap-score apps/server/src/library/internal/enrich.ts:98 'applyMetadata' has CRAP score 182.0 (threshold: 30.0, cyclomatic 13)
critical fallow/high-crap-score apps/server/src/library/internal/hydrate.ts:106 'buildUpdate' has CRAP score 110.0 (threshold: 30.0, cyclomatic 10)
minor fallow/high-crap-score apps/server/src/library/internal/media-sources.ts:109 'toLensParams' has CRAP score 42.0 (threshold: 30.0, cyclomatic 6)
major fallow/high-crap-score apps/server/src/library/internal/quality-tier.ts:18 'qualityToTier' has CRAP score 72.0 (threshold: 30.0, cyclomatic 8)
minor fallow/high-crap-score apps/server/src/library/internal/watched-state.ts:20 'deriveWatchedState' has CRAP score 30.0 (threshold: 30.0, cyclomatic 5)
critical fallow/high-crap-score apps/server/src/library/repo/collections.ts:175 'innerFilterConditions' has CRAP score 132.0 (threshold: 30.0, cyclomatic 11)
critical fallow/high-crap-score apps/server/src/library/repo/lens-pages.ts:162 'ownedFilterConditions' has CRAP score 132.0 (threshold: 30.0, cyclomatic 11)
minor fallow/high-crap-score apps/server/src/library/service.ts:64 'syncMembership' has CRAP score 30.0 (threshold: 30.0, cyclomatic 5)
minor fallow/high-crap-score apps/server/src/library/service.ts:209 'toFilters' has CRAP score 42.0 (threshold: 30.0, cyclomatic 6)
minor fallow/high-crap-score apps/server/src/library/service.ts:265 'toOwnedRow' has CRAP score 30.0 (threshold: 30.0, cyclomatic 5)
minor fallow/high-crap-score apps/server/src/library/sources/keyset.ts:107 'splitToken' has CRAP score 30.0 (threshold: 30.0, cyclomatic 5)
minor fallow/high-crap-score apps/server/src/library/sources/keyset.ts:126 'splitTriToken' has CRAP score 42.0 (threshold: 30.0, cyclomatic 6)
Architecture (1)
Severity Rule Location Description
major fallow/circular-dependency apps/server/src/library/internal/reads.ts:2 Circular dependency: apps/server/src/library/internal/reads.ts → apps/server/src/library/service.ts

Generated by fallow.

@@ -0,0 +1,40 @@
import { clearSeedLock, trySeedLock } from "../repo";
import { syncMembership } from "../service";
db: Db = getDb(),
): Promise<number> {
const base = and(eq(libraryItems.userId, userId), eq(libraryItems.owned, true));
const where = keepKeys.length > 0 ? and(base, notInArray(libraryItems.id, keepKeys)) : base;

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.

Non-blocking — footgun in the function surface.

When keepKeys is empty, where collapses to just base (userId = X AND owned = true), which would tombstone the entire library. The outer guard in syncMembership (feedKeys.length === 0 → skip) prevents this today, but the function is exported and the danger is invisible to future callers. Suggest a hard guard at the top:

Suggested change
const where = keepKeys.length > 0 ? and(base, notInArray(libraryItems.id, keepKeys)) : base;
if (keepKeys.length === 0) return 0;
const base = and(eq(libraryItems.userId, userId), eq(libraryItems.owned, true));
const where = and(base, notInArray(libraryItems.id, keepKeys));

const keys = targets.map((target) => ({ tmdbId: target.tmdbId, type: target.mediaType }));
try {
return await ctx.catalog.getMetadataBatch(keys);
} catch (err) {

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.

Non-blocking — unbounded concurrency.

Promise.all fans out every stale target simultaneously. Each buildUpdate fires 2 concurrent plugin calls (getMatchingServers + getAvailabilityQuality), so a user with 500 stale rows spawns 1000 concurrent plugin requests in one job tick. This is a background job so the timing is acceptable, but it can spike the plugin adapter's connection pool on a large initial seed.

Cheap mitigation: chunk the fan-out (e.g., 50 at a time with pLimit or a manual slice loop). Not required for merge, but worth a followup issue before the first 10k+ library user hits the hourly pass.

* them. An omitted axis stays undefined → the repo applies no filter for it.
*/
function toLensParams(params: LibraryLensQueryParsed): { filters: LensFilters; limit: number } {
const filters: LensFilters = {};

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.

Non-blocking — multi-value filter axes silently collapse (acknowledged in PR).

toLensParams receives params already parsed through libraryLensQuerySchema. The schema's arrayParam() helper coerces a single string to a one-element array and handles repeated query params correctly — but only when the values actually arrive as an array. Because the shared /media/sources/:id resolver reads params via c.req.query() (returns a single string for a repeated key, not an array), a request like ?genres=Action&genres=Comedy delivers "Comedy" not ["Action","Comedy"] to the schema, which coerces it to ["Comedy"].

Collections and facets endpoints are unaffected (they use zValidator directly). The fix is one line in the shared resolver (c.req.queries() → array). Worth a dedicated PR soon since it silently narrows multi-axis filters for every item lens.

);
--> statement-breakpoint
ALTER TABLE `canonical_metadata` ADD `collection_id` text;--> statement-breakpoint
ALTER TABLE `canonical_metadata` ADD `collection_name` text; No newline at end of file

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.

Non-blocking — missing newline at end of file. Some migration runners (and diff tooling) warn on this. Trivial fix:

Suggested change
ALTER TABLE `canonical_metadata` ADD `collection_name` text;
ALTER TABLE `canonical_metadata` ADD `collection_name` text;

(just add a trailing newline after the semicolon)

/**
* Folds the three sources into one row's denormalized projection. The
* availability lookups (`getMatchingServers`, `getAvailabilityQuality`) are the
* only awaits here; metadata and progress are already loaded. Each source is

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.

Non-blocking — silent partial on progress failure.

loadProgress swallows errors and returns an empty map. The hydrate result (HydrateResult) has no partial field, so a total CW failure produces a batch of rows with watchedState: null that look fully hydrated to the caller — the job run status shows hydrated: N with no sign of degradation. Consider either surfacing partial in HydrateResult or at minimum emitting a warn log here rather than only inside deriveWatchedState.

@github-actions

github-actions Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Code Review — PR #542: media library backend + five browse lenses

Design-doc sync ✅

Design doc docs/2026-06-02-library-backend-design.md linked in PR description, updated in this PR with per-phase status, deviations, and bug ledger. Code matches spec across all four phases. Not blocking.

Fallow baseline ✅

.fallow/dead-code-baseline.json untouched. Three new fallow zones added to .fallowrc.json (server-mod-library, server-mod-library-internal, server-schema-library) — all zone pairs, correct pattern. Not blocking.

Changesets ✅

Four changesets: @ent-mcp/server minor, @ent-mcp/client minor, @ent-mcp/plugin-sdk minor, @ent-mcp/plugin-tmdb minor. Descriptions end-user language, past tense, no impl detail. @ent-mcp/shared correctly omitted. Not blocking.


What the PR does

Implements the full library backend end-to-end:

  • Shared@ent-mcp/shared/library enums/types/schemas; collection on media items; CompactMediaItem.section for grouped lenses.
  • Schemalibrary_items (composite PK (user_id, id)), user_library_seed; migration 0004; four covering indexes.
  • Sync — tombstone-safe membership diff against collection@v1; no-wipe-on-partial/empty-feed guard; idempotent.
  • Hydrate — two-pass background projection (sortTitle, year, genres, servers, qualityTiers, watchedState, franchise); self-healing on partial failures.
  • Five lenses — A–Z, Timeline, Server, Quality via unified REGISTRY; Collections via /api/library/{collections,facets}; keyset pagination on all five.
  • Client — real data via shared useMediaRows + infinite scroll; section headers on group-key change; quality chips; facet-driven rails. Mock fixtures removed.

Security

No injection or auth gaps found. All routes sit behind requireSession; user ID comes from session, not client-supplied. Rate limiter shared with watchlist read family. Keyset tokens are opaque and total-decode (bad cursors → first page, never 400, never server-side eval). Multi-user PK isolation via composite (user_id, id) PK + FK CASCADE. Clean.

Performance

  • Sync/hydrate jobs are background-only. Availability fan-out (getMatchingServers + getAvailabilityQuality per row) never hits the read path. 30-min run timeout is generous.
  • Facets cache — 60s TTL, busted on membership change, max 5000 entries. Appropriate for the use pattern.
  • N*M concurrency concern flagged inline (hydrate.ts:84). Not blocking for merge; worth a pre-scale followup.

Testing

Extensive. New suites: sync diff/idempotency/no-resurrect/no-wipe-on-outage, franchise mapping + persist, lens keyset stability (NULL-year, null-name boundaries), json_each fan-out, facet counts, hydrate, multi-user isolation, FE hooks/section-grouping/infinite-scroll/collections/facets. 11 pre-ship bugs caught and regression-tested (ledger in design doc). Coverage looks good.

Code quality

  • Module boundary clean — drizzle stays in repo/, orchestration in service.ts, sources in sources/, infra in internal/.
  • Keyset codecs correctly split on last-space (2-part) and first+last-space (3-part); NULL-year encoded as 0; rank ordinal (not label) in quality token — all match the design.
  • buildEnrichRows dedup-free by construction — grouped lenses can repeat a title across sections without collapsing.
  • No TODOs beyond the tracked c.req.queries() followup.

Issues raised inline

File Line Severity Summary
repo/membership.ts 52 ⚠️ Non-blocking tombstoneMissing with empty keepKeys would wipe library; outer guard is correct but function is an exported footgun
internal/hydrate.ts 84 ⚠️ Non-blocking Unbounded Promise.all across stale targets; fine for now, pre-scale fix suggested
internal/hydrate.ts 103 ℹ️ Non-blocking Progress-failure partial not surfaced in HydrateResult
internal/media-sources.ts 110 ⚠️ Non-blocking Multi-value filter axes collapse (acknowledged; c.req.queries() fix deferred)
drizzle/0004_...sql 33 ℹ️ Trivial Missing EOF newline

None of these are blocking. Approved to merge after addressing the tombstoneMissing guard (trivial one-liner) or documenting the caller contract in the function JSDoc if you prefer to keep the current shape.

@electather electather merged commit adaf118 into main Jun 3, 2026
8 of 11 checks passed
@electather electather deleted the feat/library-backend branch June 3, 2026 07:52
@github-actions github-actions Bot removed the status: ready-for-review Ready for reviewer attention label Jun 3, 2026
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