commit 1bf5096d17eb06612adce750ed4b85c7b15f8359
parent f911fb85f95e590fc807d9f049bb1434d7678218
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Sun, 4 Oct 2026 20:13:50 -0400
common: the post viewer's capture panel shows the article's title and a link to article.md
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Diffstat:
3 files changed, 40 insertions(+), 7 deletions(-)
diff --git a/common/components/PostModal.test.ts b/common/components/PostModal.test.ts
@@ -55,6 +55,20 @@ test("without media or a shot (a deleted post's record): nothing at all", () =>
assert.equal(render({ ...BASE, state: "deleted" }), "");
});
+test("an article: its title and a link to its markdown; a failed one says how, with no link", () => {
+ const html = render({
+ ...BASE,
+ shot: { src: "/s.png" },
+ article: { state: "captured", title: "A Long <Read>", md: { src: "/capture/demo/1/article.md" } },
+ });
+ assert.match(html, /data-post-capture-article=""[^>]*>Article: A Long <Read>/);
+ assert.match(html, /<a href="\/capture\/demo\/1\/article\.md"[^>]*>article\.md<\/a>/);
+
+ const failed = render({ ...BASE, article: { state: "login-wall" } });
+ assert.match(failed, /Article: untitled \(login-wall\)/);
+ assert.doesNotMatch(failed, /article\.md/);
+});
+
test("media kinds by extension", () => {
assert.equal(captureMediaKind("a.JPG"), "image");
assert.equal(captureMediaKind("a.webp"), "image");
diff --git a/common/components/PostModal.tsx b/common/components/PostModal.tsx
@@ -46,6 +46,9 @@ export type PostCaptureView = {
sensitive?: boolean;
shot?: { src: string };
media: { src: string; name: string }[];
+ // The X Article the post links to, when the capture opened it: its title,
+ // how that went, and its markdown when saved.
+ article?: { state: string; title?: string; md?: { src: string } };
};
export type PostModalProps = {
@@ -265,16 +268,31 @@ export function captureMediaKind(name: string): "image" | "video" | "other" {
return "other";
}
-// The captured screenshot and media of one post. Nothing when the capture
-// holds neither (a deleted post's record, say).
+// The captured screenshot and media of one post, and a line for its article.
+// Nothing when the capture holds none of them (a deleted post's record, say).
export function PostCapturePanel({ capture }: { capture: PostCaptureView }) {
- if (!capture.shot && capture.media.length === 0) return null;
+ const { article } = capture;
+ if (!capture.shot && capture.media.length === 0 && !article) return null;
return (
<section data-post-capture="" className="mt-3 flex flex-col gap-2">
<h3 className="text-xs uppercase tracking-wide text-muted-foreground">
Captured {formatWhen(capture.capturedAt)}
{capture.sensitive ? " · behind a sensitive-media cover" : ""}
</h3>
+ {article && (
+ <p data-post-capture-article="" className="text-xs text-muted-foreground">
+ Article: {article.title ?? "untitled"}
+ {article.state !== "captured" ? ` (${article.state})` : ""}
+ {article.md && (
+ <>
+ {" · "}
+ <a href={article.md.src} className="text-primary underline">
+ article.md
+ </a>
+ </>
+ )}
+ </p>
+ )}
{capture.shot && (
<img
src={capture.shot.src}
diff --git a/common/jobs/jobKinds.ts b/common/jobs/jobKinds.ts
@@ -523,10 +523,11 @@ const JOB_KINDS: Record<string, JobKindMeta> = {
replayable: true,
queueKeyStrategy: "platform",
},
- // A screenshot and the attached media of specific archived posts, into the
- // channel's `posts-media/` (controller/capturePosts.ts). On the PLATFORM
- // queue, like fetch-posts: each post is a page load and a download against
- // the same source, so it serialises with the fetch and shares its backoff.
+ // A screenshot and the attached media of specific archived posts (and the X
+ // Article a post links to), into the channel's `posts-media/`
+ // (controller/capturePosts.ts). On the PLATFORM queue, like fetch-posts:
+ // each post is a page load and a download against the same source, so it
+ // serialises with the fetch and shares its backoff.
// Drainable (it stops between posts) and replayable (the ids are in the
// spec; posts already captured are skipped on a re-run).
"capture-posts": {