Repository navigation
feat(console): filter and page the marketplace catalog through the list door - #31288
Merged
Merged
Conversation
The marketplace page no longer reads the first 100 listings and filters them in the browser. The search text, the facet selection and the page window travel with the list read, spelled as GET /api/v1/marketplace/packages reads them: businessFunctions, industries and solutionTypes as one comma-joined parameter each, maturity as one value, a facet with nothing selected left out, limit and offset. A pager walks the server's total. A drawn facet now offers every spec value without a count, since one page of the filtered catalog cannot count the catalog, and stays drawn once some answered listing carried it. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FEkQ5npnoTzeRFxu1Mj26Q
…e server Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FEkQ5npnoTzeRFxu1Mj26Q
Contributor
Author
|
domain:ui#1 · os-project-manager · session_01FEkQ5nLA7wiVY5YWjT9Svr 席位核对 · ACCEPT · 2026-11-07T22:44Z · head
|
This was referenced Nov 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Requested by Leehom · project thread
Fixes #31267
Clause-②: no
Metadata-protocol: none
维护者速读
GET /api/v1/marketplace/packages:businessFunctions/industries/solutionTypes各一个逗号拼接的参数,没选的维度不发,q在停止输入 300 毫秒后发,limit/offset由卡片下方的翻页器驱动(每页 24 条)。listMarketplacePackages还接受maturity,页面本身不选它。@object-ui/app-shell的市场页(随@object-ui/console的构建发出),patch。可见变化:维度按钮现在列出规格声明的全部取值、不再显示数量(一页筛选结果数不出全目录,列表接口也不返回计数);搜索改由服务端匹配(名称与应用 ID,与占位文字一致),不再匹配描述和本地化名称。默认配置下请求原样转发 cloud,cloud 按同名参数筛选要等 cloud#3661。回滚即 revert 本 PR。ui/packages/app-shell/src/console/marketplace/MarketplacePage.tsx(catalogRequest、读取 effect、翻页器)、marketplaceApi.ts的listMarketplacePackages(参数拼法)、marketplaceTaxonomy.ts的facetListParams与widenServedFacets。pnpm --dir ui exec vitest run packages/app-shell/src/console/marketplace/__tests__/MarketplacePage.facets-31011.test.tsx packages/app-shell/src/console/marketplace/__tests__/marketplaceApi.listQuery-31267.test.ts,预期 17 个用例全绿(点按钮、打字、翻页,断言列表请求收到的查询串)。What changes
listMarketplacePackages(marketplaceApi.ts) takesMarketplaceListParams:q,limit,offset, and the four filters ofMarketplaceSearchRequestSchemaspelled as the published spec names them,businessFunctions,industries,solutionTypes(plural) andmaturity. Each list filter goes out as ONE comma-joined parameter; a list that selects nothing is left out, never sent empty;maturityis one value;categoryis never sent. These are exactly the three shapes the list door refuses with 400VALIDATION_ERROR(empty facet, repeated facet key, retiredcategory), so the console sends none of them.MarketplacePage.tsxno longer reads{ limit: 100 }and filters in the browser. Its read iscatalogRequest: the debounced search text asq, the selection throughfacetListParams,limit: CATALOG_PAGE_SIZE(24) and the page'soffset. One effect reads whenever that request's spelling changes; each new read retires the pending one, so only the newest settles the page (the existingloadSeqmechanism). The first read, Refresh, Retry and the read after an install also read the installs and the org's packages; a page turn or a new selection reads the catalog alone. The search box reads once typing pauses forSEARCH_DEBOUNCE_MS(300 ms). A new search or selection reads from the first page. The cards drawn are the door's answer, unfiltered. Empty answers say "No apps match your filters." under a search or a selection and "No apps have been approved…" without one.totalspans more than one page: Previous, "Page N of M", Next, disabled at either end and while a read is pending. It reuses the existing keyscommon.previous,common.nextandtable.pageInfo; no i18n key is added, removed or reworded.MarketplaceFacetFilters.tsx,marketplaceTaxonomy.ts). A drawn facet offers every value the spec declares, in the spec's order, read from the schemas'.optionsas before. The per-value count is gone, and so arematchesFacetsandfacetOptions: one page of the filtered catalog cannot count the catalog, and the list door answers no counts. Which facets are drawn isServedFacets, widened bywidenServedFacetsfrom each answer: a facet appears once some answered listing carries it (the solution type once one declares a type) and stays drawn while a selection narrows the page, so a catalog serving no taxonomy still draws no filter (the console marketplace: facet filters (business function, industry, solution type) and a Beta badge replace the single category chip row — reads the spec taxonomy (objectstack#30904) #31011 behaviour).solutionTypesreads a row without the column asapp,maturityasstable). The one console reading that remains,effectiveSolutionTypeon the package page, isDEFAULT_SOLUTION_TYPEfrom@objectstack/spec/marketplace, the same constant the door imports; the Beta badge readsmaturity === 'beta', so an absent maturity draws asstable. No second copy of either default is added..changeset/31267-marketplace-server-facets.md,@object-ui/app-shellpatch, with both declaration lines (precedent:.changeset/30330-marketplace-readme-fits-page.md, an app-shell-only change shipped through@object-ui/console'sdist).Tests
MarketplacePage.facets-31011.test.tsx, the page's existing harness, rewritten for the server read (15 cases). The catalog stub records each list read's query string and answers from a per-test function. Every click, keystroke and page turn goes through the rendered page; the assertions read the query the data layer sent: keys, comma-joined values in the spec order, an omitted empty facet, nocategory, no empty value, at most one occurrence of each key,limit/offset. One case answers ahrread with listings that carry nohrand asserts both are drawn: the console filters nothing. The pager case walks three pages byoffsetand checks that a selection on page 2 reads from offset 0. The console marketplace: facet filters (business function, industry, solution type) and a Beta badge replace the single category chip row — reads the spec taxonomy (objectstack#30904) #31011 cases that still hold (facets hide with no taxonomy, per-facet hiding, out-of-vocabulary values drawn nowhere, non-array columns, primary-function and Beta badges) stay, read through the server answer.marketplaceApi.listQuery-31267.test.ts(new, 2 cases): the exact URLlistMarketplacePackagesbuilds for all four filters,maturityincluded, and that empty lists and no filters send nothing extra.MarketplacePage.searchPlaceholder-10900.test.tsx: its third case pinned the browser-side search; it now asserts the box's text reaches the read as trimmedqfrom offset 0, and that the door's empty answer draws no card. The placeholder's promise (name or app ID) is the door'sqmatching,catalogRowMatchesin@objectstack/cloud-connection's marketplace proxy.Acceptance notes
GET /api/v1/marketplace/packagesanswers{ items, total, limit, offset }with no aggregation. The spec does declare an optionalfacetsmember onMarketplaceSearchResponseSchema(one count list per facet), but no in-repo producer serves it, so the console does not read it. If counts are wanted back, the list door serving those counts is the route; noted, not filed.q(proxy path) matchesdisplay_nameandmanifest_id, which is what the placeholder says ("by name or app ID"). Matching the description or a localized name is gone; noted, not filed.OS_MARKETPLACE_PUBLIC_BASE_URLunset, the proxy validates the query and forwards it verbatim to the cloud list route, which adopts the same names on cloud#3661 (carrier: cloud). Until that lands, a deployment on the default path may answer a facet read unfiltered; the console draws what it is answered. The R2 fast path filters in-repo.listMarketplacePackages(card item 1) and pinned there; the page selects no maturity, as before (maturity is a badge, not a facet, in the spec's own words).Verification
All on head
aad134ccunless named. Exit codes captured before any pipe.Rendered and unit tests: 58/58 files, 515/515 tests
pnpm --dir ui exec vitest run packages/app-shell/src/console/marketplace/under the verification lock:Test Files 58 passed (58),Tests 515 passed (515), VERDICT command-exit 0.pnpm --filter @object-ui/app-shell run type-check(bothtsconfig.jsonandtsconfig.test.json): exit 0.eslinton the seven changed files: 0 errors (warnings only; the read effect'sexhaustive-depswarning was already onmain's effect, and the debounce effect adds one of the same kind).pnpm --dir ui check:new-line-citations: 0 new citations, exit 0.Ablations: six mutations, each red, each restored to the HEAD blob
node scripts/ablation-replace.mjsonf7c43530, running the three changed test files; ui vitest reads app-shell fromsrc/, so no build sits between mutation and run. Every leg printedok restored: blob == HEADwithgit diff HEADempty.if (values)forif (values && values.length > 0)inlistMarketplacePackages): 1 red,leaves out a filter that selects nothing, and never sends category....facetParams,deleted fromcatalogRequest): 7 red, every case that clicks a facet. The first attempt at B used a replacement that was a substring of the anchor; the script refused it (rise 0, nothing ran), so it was redone with--delete.usp.appendper value): 3 red.limit: 100, nooffset): 6 red, including the pager and the search placeholder case.facetListParams: 1 red,businessFunctions=sales,hrexpected.served.businessFunctions ||removed inwidenServedFacets): 1 red,a drawn facet stays drawn when a selection narrows the page.Derived gates: 37 derived, 37 run, all exit 0
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, reconciled with--ran:37 derived famil(ies) accounted for — 37 run, 0 NOT-MEASURED.f7c43530(the code commit):check-closing-keyword-parity(and--self-test),check-comment-mask-corpus,check-sdui-manifest(and--self-test),pnpm --dir ui exec vitest run scripts/, speccheck:duration-unit-keys,check:error-code-provenance,check:cross-package-test-inputs,check:dispatcher-error-vocabulary,check:driver-memory-census,check:engine-double-contract,check:gitlink-declared,check:nul-bytes,check:published-files,check:query-options-erasure,check:refd-timer-probe,check:registry-log-declared,check:rest-log-spy-declared,check:skill-identifier-liveness,check:test-source-alias,check:tier-file-adoption,check:type-check-coverage,check:type-check-debt,check:undeclared-dep-imports,check:watch-hint-literal,check:where-matcher: all exit 0.aad134cc:check-adr-0087-registration(--base origin/mainand--self-test),check-changeset-no-major,check-empty-changeset(--base origin/mainand--self-test),ci/snapshot-next --self-test,pm/release-rehearsal-clone --self-test,release-pending-publish --self-test,check:changeset-gate-self-tests,check:closed-ceremony-text: all exit 0.f7c43530..aad134ccadds the changeset file only, and the derivation maps that path to exactly those 10 families, so the 27 were not re-run on the changeset commit.!in the title, no(narrowing)arm).Generated by Claude Code