fix: 최근 기록에 Open Question 을 싣고, 주제 링크를 탐색 필터로 돌린다

게시한 질문이 홈 최근 기록에 나오지 않았다. 계약의 `LatestEntry.entryType` 이 네 값만
허용해 백엔드가 담을 수 없었다 — design-package ef49d3a 에서 `QUESTION` 을 더했고 여기서
계약을 다시 생성한다. 포트의 `LatestRecordEntry.entryType` 도 같이 넓히고, 홈의 표기는
목록의 다른 이름들과 같은 규칙(대문자에 공백)을 따라 `OPEN QUESTION` 으로 적는다.

문서 머리말의 주제 링크는 `/topics/:slug` 로 가고 있었는데, 그 화면은 `jpa`/`authentication`
/`redis` 세 개를 하드코딩해 두고 있어 실제 주제는 무엇이든 404 가 됐다. 게시한 모든 문서의
주제 링크가 거기로 갔다.

주제 페이지를 채우는 대신 링크를 탐색 필터로 돌린다. 지금 그 페이지만 줄 수 있는 것 —
설명, 범위, 선별한 대표 기록 — 이 전부 비어 있고, Studio 의 주제 만들기는 `{name, slug}` 만
보내므로 설명을 쓸 칸조차 없다. 독자가 거기서 기대하는 것은 같은 주제의 기록 목록이고,
그것은 `/explore?topic=` 이 그대로 준다. 주제가 여러 개가 되고 설명을 쓸 수 있게 되면 그때
페이지를 만드는 것이 순서다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0189NzCryfeqDzS81EWidnBx
This commit is contained in:
DongHyeonka
2026-08-26 23:44:20 +09:00
co-authored by Claude Opus 5
parent ad8f322b43
commit 2632850322
9 changed files with 26 additions and 13 deletions
+5 -5
View File
@@ -38,28 +38,28 @@
}, },
"contractSet": { "contractSet": {
"setAlgorithm": "CA_CONTRACT_SET_V1", "setAlgorithm": "CA_CONTRACT_SET_V1",
"setDigest": "sha256:104bab24221f6d1a4c5fa7df68f5d027d227325a88fa507ebfcccebb439eb7c4", "setDigest": "sha256:cdcfb628a502d71596f1162726eb395aad0f5f92cf05fd77d304f8e51c81b2fc",
"packages": [ "packages": [
{ {
"packageId": "@tech-log/management-contract", "packageId": "@tech-log/management-contract",
"version": "1.0.0", "version": "1.0.0",
"digest": "sha256:72650735061fde627f5037571eb986cb758f44a546f065c88408399f8eec4a55", "digest": "sha256:72650735061fde627f5037571eb986cb758f44a546f065c88408399f8eec4a55",
"runtimeProtocolVersion": 1, "runtimeProtocolVersion": 1,
"sourceRevision": "0ffbc28" "sourceRevision": "ef49d3a"
}, },
{ {
"packageId": "@tech-log/public-contract", "packageId": "@tech-log/public-contract",
"version": "2.1.0", "version": "2.1.0",
"digest": "sha256:79949b286e2b4d28333d3376945960a319d247b675b23a73f50b75ce10994c5e", "digest": "sha256:7eb668e39e279e49767306dd36e1dd51302071c39d78495d21307bbd9676220e",
"runtimeProtocolVersion": 1, "runtimeProtocolVersion": 1,
"sourceRevision": "0ffbc28" "sourceRevision": "ef49d3a"
}, },
{ {
"packageId": "@tech-log/studio-contract", "packageId": "@tech-log/studio-contract",
"version": "3.1.0", "version": "3.1.0",
"digest": "sha256:18dd46898be64b07f7e826409d19347512613ee2e22420028a4a0644f50f37dd", "digest": "sha256:18dd46898be64b07f7e826409d19347512613ee2e22420028a4a0644f50f37dd",
"runtimeProtocolVersion": 1, "runtimeProtocolVersion": 1,
"sourceRevision": "0ffbc28" "sourceRevision": "ef49d3a"
} }
] ]
} }
@@ -173,7 +173,7 @@ export type PublicTopic = {
*/ */
export type LatestRecordEntry = { export type LatestRecordEntry = {
id: string; id: string;
entryType: "CASE" | "REFERENCE" | "PROJECT_ACTIVITY" | "RELEASE"; entryType: "CASE" | "REFERENCE" | "QUESTION" | "PROJECT_ACTIVITY" | "RELEASE";
title: string; title: string;
summary: string; summary: string;
path: string; path: string;
@@ -2,7 +2,7 @@
"packageId": "@tech-log/management-contract", "packageId": "@tech-log/management-contract",
"version": "1.0.0", "version": "1.0.0",
"digest": "sha256:72650735061fde627f5037571eb986cb758f44a546f065c88408399f8eec4a55", "digest": "sha256:72650735061fde627f5037571eb986cb758f44a546f065c88408399f8eec4a55",
"sourceRevision": "0ffbc28", "sourceRevision": "ef49d3a",
"operationIds": [ "operationIds": [
"createCaseDraft", "createCaseDraft",
"getCaseForEdit", "getCaseForEdit",
@@ -1,8 +1,8 @@
{ {
"packageId": "@tech-log/public-contract", "packageId": "@tech-log/public-contract",
"version": "2.1.0", "version": "2.1.0",
"digest": "sha256:79949b286e2b4d28333d3376945960a319d247b675b23a73f50b75ce10994c5e", "digest": "sha256:7eb668e39e279e49767306dd36e1dd51302071c39d78495d21307bbd9676220e",
"sourceRevision": "0ffbc28", "sourceRevision": "ef49d3a",
"operationIds": [ "operationIds": [
"getPublicSite", "getPublicSite",
"getPublicHome", "getPublicHome",
@@ -454,7 +454,7 @@ export interface components {
}; };
LatestEntry: { LatestEntry: {
/** @enum {string} */ /** @enum {string} */
entryType: "CASE" | "REFERENCE" | "PROJECT_ACTIVITY" | "RELEASE"; entryType: "CASE" | "REFERENCE" | "QUESTION" | "PROJECT_ACTIVITY" | "RELEASE";
title: string; title: string;
summary: string; summary: string;
path: string; path: string;
@@ -1022,6 +1022,7 @@ components:
enum: enum:
- CASE - CASE
- REFERENCE - REFERENCE
- QUESTION
- PROJECT_ACTIVITY - PROJECT_ACTIVITY
- RELEASE - RELEASE
title: title:
@@ -2,7 +2,7 @@
"packageId": "@tech-log/studio-contract", "packageId": "@tech-log/studio-contract",
"version": "3.1.0", "version": "3.1.0",
"digest": "sha256:18dd46898be64b07f7e826409d19347512613ee2e22420028a4a0644f50f37dd", "digest": "sha256:18dd46898be64b07f7e826409d19347512613ee2e22420028a4a0644f50f37dd",
"sourceRevision": "0ffbc28", "sourceRevision": "ef49d3a",
"operationIds": [ "operationIds": [
"getStudioSession", "getStudioSession",
"getStudioDashboard", "getStudioDashboard",
@@ -19,7 +19,13 @@ export function PublicDocumentHeader({ record }: { record: PublicRecord }) {
{kindLabels[record.kind]} {kindLabels[record.kind]}
</Link> </Link>
<span aria-hidden="true">/</span> <span aria-hidden="true">/</span>
<Link to={`/topics/${record.topicSlug}`}>{record.topic}</Link> {/*
주제 페이지가 아니라 그 주제로 거른 탐색으로 보낸다. `/topics/:slug` 는 세 개를
하드코딩해 두고 있어 실제 주제는 무엇이든 404 가 되고, 설명·범위·대표 기록이 전부
비어 있어 지금 채울 내용도 없다. 독자가 여기서 기대하는 것 — 같은 주제의 기록 목록 —
은 탐색 필터가 그대로 준다.
*/}
<Link to={`/explore?topic=${encodeURIComponent(record.topicSlug)}`}>{record.topic}</Link>
<span aria-hidden="true">/</span> <span aria-hidden="true">/</span>
<Link to={`/projects/${record.projectSlug}`}> <Link to={`/projects/${record.projectSlug}`}>
{record.projectTitle} {record.projectTitle}
@@ -50,6 +50,11 @@ function optionalString(value: unknown): string | undefined {
* 릴리스는 Publication 파이프라인을 거치지 않아 그 투영에 행이 없다. 그래서 릴리스만 따로 읽어 * 릴리스는 Publication 파이프라인을 거치지 않아 그 투영에 행이 없다. 그래서 릴리스만 따로 읽어
* 시간순으로 합친다. * 시간순으로 합친다.
*/ */
const latestTypeLabels: Readonly<Record<string, string>> = {
PROJECT_ACTIVITY: "PROJECT ACTIVITY",
QUESTION: "OPEN QUESTION",
};
async function getLatestEntries( async function getLatestEntries(
publicContent: PublicContentQueries, publicContent: PublicContentQueries,
): Promise<LatestEntry[]> { ): Promise<LatestEntry[]> {
@@ -60,7 +65,8 @@ async function getLatestEntries(
const recordTimeline: LatestEntry[] = records.map((entry) => ({ const recordTimeline: LatestEntry[] = records.map((entry) => ({
id: entry.id, id: entry.id,
typeLabel: entry.entryType === "PROJECT_ACTIVITY" ? "PROJECT ACTIVITY" : entry.entryType, // 목록의 다른 이름들과 같은 자리에 놓이므로 표기도 같은 규칙을 쓴다 — 대문자에 공백.
typeLabel: latestTypeLabels[entry.entryType] ?? entry.entryType,
title: entry.title, title: entry.title,
summary: entry.summary, summary: entry.summary,
date: dateLabel(entry.publishedAt), date: dateLabel(entry.publishedAt),