Skip to content

Commit 799df30

Browse files
committed
Project screenshot thumbnail work
1 parent 6652c48 commit 799df30

5 files changed

Lines changed: 36 additions & 12 deletions

File tree

apps/gif-service/src/compile.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,9 @@ export async function compileProject(lang: string, code: string): Promise<Buffer
7070
});
7171
const cim = files['zout/in.cim'];
7272
if (!cim || cim.length === 0) {
73-
throw errors.length ? errors : new Error('zmac produced no output');
73+
// errors are raw stderr strings; join them so the reason
74+
// surfaces (logged + returned) rather than "unknown error".
75+
throw new Error(errors.join('\n').slice(0, 500) || 'zmac produced no output');
7476
}
7577
// Wrap the raw code image (ORG 0x8000) into a self-loading TAP.
7678
const { bin2tap } = await import('pasmo');

apps/gif-service/src/routes/screenshot.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -84,16 +84,17 @@ router.get('/:id', async (req: Request, res: Response) => {
8484
} catch (err) {
8585
if (err instanceof CompileError) {
8686
// Unrenderable (e.g. unsupported lang, or bad source) → web falls
87-
// back to the cartridge. Short negative-cache: enough to avoid
88-
// re-rendering on every page load, but it self-heals quickly after a
89-
// deploy that adds rendering support (the key is project-based, so a
90-
// server capability change can't otherwise invalidate it).
87+
// back to the cartridge. Log + return the reason so failures are
88+
// inspectable (docker logs and the response body) instead of a silent
89+
// 422. Short negative-cache: avoids re-rendering on every page load
90+
// but self-heals quickly after a deploy that adds render support.
91+
console.log(`Screenshot ${id}: not rendered: ${err.message.slice(0, 300)}`);
9192
res.setHeader('Cache-Control', 'public, max-age=300');
92-
res.status(422).end();
93+
res.status(422).type('text/plain').send(err.message.slice(0, 300));
9394
return;
9495
}
95-
console.error('Screenshot error:', err);
96-
res.status(500).end();
96+
console.error(`Screenshot ${id} error:`, err);
97+
res.status(500).type('text/plain').send('Internal error');
9798
}
9899
});
99100

22.5 KB
Loading
23.9 KB
Loading

apps/web/src/components/ProjectThumbnail.jsx

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
import React, { useState } from "react";
22

3+
// Size of the ZX rainbow ribbon baked into the bottom-right corner of a
4+
// screenshot thumbnail (square, so the diagonal stays at 45deg). Tweak here.
5+
const RIBBON_SIZE = "44px";
6+
37
// Tilted corner thumbnail for a project card. The box is 4:3 to match the
48
// Spectrum screenshot's aspect, so the rendered PNG from gif-service fills it
5-
// exactly with no crop (only the tilt trims the corners). Falls back to the
6-
// static cartridge graphic when there's no screenshot yet, the project isn't
7-
// public, or it can't be rendered (e.g. zmac/sdcc).
9+
// exactly with no crop (only the tilt trims the corners). Screenshots get a
10+
// small diagonal rainbow ribbon in the corner (the same one the cartridge
11+
// shows full-size). Falls back to the static cartridge graphic when there's no
12+
// screenshot yet, the project isn't public, or it can't be rendered (e.g.
13+
// zmac/sdcc) — that already carries the ribbon, so no overlay is added.
814
export default function ProjectThumbnail({ projectId, updatedAt }) {
915
const [failed, setFailed] = useState(false);
1016
const version = updatedAt ? Date.parse(updatedAt) || 0 : 0;
@@ -28,11 +34,26 @@ export default function ProjectThumbnail({ projectId, updatedAt }) {
2834
}}
2935
>
3036
<img
31-
src={showShot ? `/screenshots/${projectId}.png?v=${version}` : "/assets/images/zx-square.png"}
37+
src={showShot ? `/screenshots/${projectId}.png?v=${version}` : "/assets/images/zx-card.png"}
3238
alt=""
3339
onError={showShot ? () => setFailed(true) : undefined}
3440
style={{ width: "100%", height: "100%", objectFit: "cover" }}
3541
/>
42+
{showShot && (
43+
<img
44+
src="/assets/images/zx-ribbon.png"
45+
alt=""
46+
aria-hidden="true"
47+
style={{
48+
position: "absolute",
49+
right: 0,
50+
bottom: 0,
51+
width: RIBBON_SIZE,
52+
height: RIBBON_SIZE,
53+
pointerEvents: "none",
54+
}}
55+
/>
56+
)}
3657
</div>
3758
);
3859
}

0 commit comments

Comments
 (0)