fix(detail+docker): per-aspect template preview + Debian frontend base
CI/CD / CI · Web (tsc) (push) Successful in 1m17s
CI/CD / Deploy · full stack (push) Failing after 15s

- Template detail page now shows the render matching the SELECTED aspect (poster +
  preview video) instead of the 16:9 cover cropped into a 9:16/1:1 box. TemplateVariant
  carries per-aspect image/previewVideo; fetchTemplateVariants + the detail page wire them.
- AppShowcase3D ships a distinct preview video per aspect (seed PERASPECT_VIDEO).
- Frontend Dockerfile: Alpine -> node:20-slim (glibc). Fixes next-swc ("ld-linux..."
  load failure that broke `next build` once libc6-compat was removed) AND the original
  CI Alpine-CDN issue. Healthcheck switched to node (slim has no wget).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-21 23:04:04 +03:30
parent 60759f35b4
commit 863b9503b3
10 changed files with 33 additions and 18 deletions
+1 -1
View File
@@ -27,7 +27,7 @@ async function resolveTemplate(id: string): Promise<VideoCatalogTemplate | null>
const base = adminProjectToCatalogTemplate(admin);
const variants = (await fetchTemplateVariants(id))
.filter((v) => SUPPORTED_ASPECTS.has(v.aspect as TemplateDetailAspectRatio))
.map((v) => ({ aspect: v.aspect as TemplateDetailAspectRatio, projectId: v.projectId }));
.map((v) => ({ aspect: v.aspect as TemplateDetailAspectRatio, projectId: v.projectId, image: v.image, previewVideo: v.previewVideo }));
return { ...base, variants };
}
return VIDEO_TEMPLATES_CATALOG.find((item) => item.id === id) ?? null;
@@ -32,8 +32,10 @@ export function TemplateDetailPreview({
onSelectAspect,
}: TemplateDetailPreviewProps) {
const aspectOptions = getTemplateDetailAspectRatios(template);
const posterSrc = template.coverImageUrl ?? getVideoTemplateImageSrc(template.id);
const videoSrc = template.previewVideoUrl ?? getTemplatePreviewVideoSrc(template.id);
// Use the render that matches the selected aspect (not the 16:9 cover cropped).
const variant = template.variants?.find((v) => v.aspect === selectedAspect);
const posterSrc = variant?.image ?? template.coverImageUrl ?? getVideoTemplateImageSrc(template.id);
const videoSrc = variant?.previewVideo ?? template.previewVideoUrl ?? getTemplatePreviewVideoSrc(template.id);
return (
<div>
+8 -3
View File
@@ -214,13 +214,18 @@ export async function fetchProject(slug: string): Promise<AdminProject | null> {
* studio copies. Returns [] when none / unreachable. */
export async function fetchTemplateVariants(
slug: string
): Promise<Array<{ aspect: string; projectId: string }>> {
): Promise<Array<{ aspect: string; projectId: string; image?: string; previewVideo?: string }>> {
const c = await safeGet<{
projects?: Array<{ id?: string; aspect?: string; is_published?: boolean }>;
projects?: Array<{ id?: string; aspect?: string; is_published?: boolean; image?: string | null; full_demo?: string | null; demo?: string | null }>;
}>(`/v1/templates/${encodeURIComponent(slug)}`);
return (c?.projects ?? [])
.filter((p) => p?.id && p?.is_published && p?.aspect)
.map((p) => ({ aspect: p.aspect as string, projectId: p.id as string }));
.map((p) => ({
aspect: p.aspect as string,
projectId: p.id as string,
image: p.image ?? undefined,
previewVideo: p.full_demo ?? p.demo ?? undefined,
}));
}
/** True when the gateway content endpoint is reachable. */
+4
View File
@@ -63,6 +63,10 @@ export type TemplateDetailAspectRatio = "16:9" | "1:1" | "9:16";
export interface TemplateVariant {
aspect: TemplateDetailAspectRatio;
projectId: string;
/** Per-aspect thumbnail + preview video so the detail page shows the render
* that actually matches the selected aspect (not the 16:9 cover cropped). */
image?: string;
previewVideo?: string;
}
export const TEMPLATE_STYLE_COUNT = 4;