Skip to content

Commit e711d11

Browse files
prioritize prototypes in Community filters (#6980)
* prioritize prototypes in Community filters * test(web): select slides in modal mapping coverage Generated-By: looper 0.11.8 (runner=fixer, agent=codex) * fix(web): fall back to an available Community facet Generated-By: looper 0.11.8 (runner=fixer, agent=codex) * test(web): select slides before deck preview assertion --------- Co-authored-by: Amy <1184569493@qq.com>
1 parent 4eacda5 commit e711d11

3 files changed

Lines changed: 40 additions & 17 deletions

File tree

apps/web/src/components/CommunityTemplatePreview.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ export type TemplateDemo = {
5959
prompt: string;
6060
};
6161

62-
export const TEMPLATE_TYPE_ORDER: TemplateType[] = ['Slides', 'Prototype', 'Live Artifact', 'Image', 'Video', 'HyperFrames', 'Audio'];
62+
export const TEMPLATE_TYPE_ORDER: TemplateType[] = ['Prototype', 'Slides', 'Live Artifact', 'Image', 'Video', 'HyperFrames', 'Audio'];
6363

6464
/** The Community grid is the plugin catalogue seen through the artifact a user
6565
* wants to make. Membership comes from the shared facet derivation in

apps/web/src/components/CommunityView.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ export function CommunityView({ onRemixTemplate, onUsePrompt, onUsePlugin }: Com
9999
// chip (飞书 recvqxDuYM6Uxk). Keep the raw record here: the modal renders
100100
// from `InstalledPluginRecord`, not from the card view-model.
101101
const [detailsRecord, setDetailsRecord] = useState<InstalledPluginRecord | null>(null);
102-
const [activeType, setActiveType] = useState<TemplateType>('Slides');
102+
const [selectedType, setSelectedType] = useState<TemplateType | null>(null);
103103
const [activeSubtype, setActiveSubtype] = useState('All');
104104
// Remix hands off to a fire-and-forget parent callback
105105
// (`onRemixTemplate` returns void) that kicks off a real POST /api/projects
@@ -153,6 +153,9 @@ export function CommunityView({ onRemixTemplate, onUsePrompt, onUsePlugin }: Com
153153
const typeOptions = TEMPLATE_TYPE_ORDER.filter((type) =>
154154
templates.some((template) => template.type === type),
155155
);
156+
const activeType = selectedType && typeOptions.includes(selectedType)
157+
? selectedType
158+
: typeOptions[0];
156159
const subtypeOptions = Array.from(new Set(
157160
templates
158161
.filter((template) => template.type === activeType && template.subtype)
@@ -280,7 +283,7 @@ export function CommunityView({ onRemixTemplate, onUsePrompt, onUsePlugin }: Com
280283
filter_value: type,
281284
...workspaceDimensions,
282285
});
283-
setActiveType(type);
286+
setSelectedType(type);
284287
setActiveSubtype('All');
285288
}}
286289
>

apps/web/tests/community-view.test.tsx

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -201,20 +201,34 @@ describe('CommunityView catalogue source', () => {
201201

202202
expect(fetchMock).toHaveBeenCalledWith('/api/plugins', undefined);
203203

204-
// Slides leads, and it carries exactly the two deck plugins the daemon
205-
// served — not a bundled demo array.
204+
// Prototype leads, followed by Slides; both come from the daemon-served
205+
// plugin catalogue rather than a bundled demo array.
206206
const facets = readFacets();
207-
expect(facets.map((facet) => facet.label)).toEqual(['Slides', 'Prototype', 'Image']);
207+
expect(facets.map((facet) => facet.label)).toEqual(['Prototype', 'Slides', 'Image']);
208208

209209
// The card footer reads "<type> · <sub-facet>", both resolved from the
210210
// shared plugins-home taxonomy. Asserted before the tab walk below, which
211211
// leaves a different facet active.
212212
expect(renderedCards().map((card) => card.querySelector('.community-template-card__foot span')?.textContent))
213-
.toEqual(['Slides · Fundraising pitch', 'Slides · B2B sales']);
213+
.toEqual(['Prototype · Landing / marketing']);
214214

215-
// Slides leads and carries exactly the two deck plugins the daemon served
216-
// — not a bundled demo array.
217-
expect(readFacetCardCounts()).toEqual([2, 1, 1]);
215+
expect(readFacetCardCounts()).toEqual([1, 2, 1]);
216+
});
217+
218+
it('falls back to the first available type when the catalogue has no Prototype templates', async () => {
219+
fetchMock.mockImplementation(async () => new Response(JSON.stringify({
220+
plugins: [PITCH_DECK, SALES_DECK],
221+
}), {
222+
status: 200,
223+
headers: { 'content-type': 'application/json' },
224+
}));
225+
226+
await renderCommunity();
227+
228+
const facets = readFacets();
229+
expect(facets.map((facet) => facet.label)).toEqual(['Slides']);
230+
expect(facets[0]!.tab.classList.contains('is-active')).toBe(true);
231+
expect(renderedCards()).toHaveLength(2);
218232
});
219233

220234
it('leaves hidden and design-system plugins out of the gallery', async () => {
@@ -234,8 +248,9 @@ describe('CommunityView catalogue source', () => {
234248
const pills = Array.from(
235249
document.querySelectorAll('.community-template-view__subtabs button'),
236250
).map((button) => button.textContent?.trim());
237-
expect(pills).toEqual(['All', 'Fundraising pitch', 'B2B sales']);
251+
expect(pills).toEqual(['All', 'Landing / marketing']);
238252

253+
fireEvent.click(readFacets().find((facet) => facet.label === 'Slides')!.tab);
239254
fireEvent.click(screen.getByRole('button', { name: 'B2B sales' }));
240255
expect(renderedCards()).toHaveLength(1);
241256
});
@@ -244,13 +259,15 @@ describe('CommunityView catalogue source', () => {
244259
describe('CommunityView previews', () => {
245260
it('centres deck media in the 16:9 preview crop while legacy bakes are being replaced', async () => {
246261
await renderCommunity();
262+
fireEvent.click(readFacets().find((facet) => facet.label === 'Slides')!.tab);
247263

248264
expect(renderedCards()[0]!.querySelector('.community-template-card__preview.is-deck'))
249265
.not.toBeNull();
250266
});
251267

252268
it('shows the plugin\'s own poster on the card and its live page in the full details modal', async () => {
253269
await renderCommunity();
270+
fireEvent.click(readFacets().find((facet) => facet.label === 'Slides')!.tab);
254271

255272
// Card thumbnail: the daemon-baked poster for that plugin.
256273
const thumb = renderedCards()[0]!.querySelector('img.plugins-home__media-img');
@@ -276,6 +293,7 @@ describe('CommunityView previews', () => {
276293
// floor even though the daemon still attached it and the classifier still
277294
// resolved it.
278295
await renderCommunity();
296+
fireEvent.click(readFacets().find((facet) => facet.label === 'Slides')!.tab);
279297

280298
const card = renderedCards()[0]!;
281299
expect(card.querySelector('img.community-template-thumb__image')).toBeNull();
@@ -306,6 +324,7 @@ describe('CommunityView previews', () => {
306324
// The B2B deck ships an html preview and no bake, so there is no media spec
307325
// to mount — that card must still fall back to the stylized paper tile.
308326
await renderCommunity();
327+
fireEvent.click(readFacets().find((facet) => facet.label === 'Slides')!.tab);
309328

310329
const card = renderedCards()[1]!;
311330
expect(card.querySelector('.community-template-thumb__paper')).not.toBeNull();
@@ -374,8 +393,8 @@ describe('CommunityView remix', () => {
374393

375394
expect(onRemix).toHaveBeenCalledTimes(1);
376395
expect(onRemix.mock.calls[0]![0]).toEqual({
377-
templateId: 'example-fundraising-deck',
378-
prompt: 'A decision-grade seed round narrative.',
396+
templateId: 'example-landing-prototype',
397+
prompt: 'A conversion-focused SaaS landing page.',
379398
});
380399
});
381400

@@ -420,6 +439,7 @@ describe('CommunityView remix', () => {
420439
// handler invocation really runs (see the rapid-click note above).
421440
const onRemix = vi.fn();
422441
await renderCommunity({ onRemixTemplate: onRemix });
442+
fireEvent.click(readFacets().find((facet) => facet.label === 'Slides')!.tab);
423443

424444
fireEvent.click(renderedCards()[0]!);
425445
await waitFor(() => {
@@ -459,10 +479,10 @@ describe('CommunityView use handoff', () => {
459479
fireEvent.click(screen.getAllByRole('button', { name: 'Use' })[0]!);
460480

461481
expect(onUsePrompt).toHaveBeenCalledWith({
462-
templateId: 'example-fundraising-deck',
463-
prompt: 'A decision-grade seed round narrative.',
464-
chipId: 'deck',
465-
projectKind: 'deck',
482+
templateId: 'example-landing-prototype',
483+
prompt: 'A conversion-focused SaaS landing page.',
484+
chipId: 'prototype',
485+
projectKind: 'prototype',
466486
});
467487
});
468488

0 commit comments

Comments
 (0)