feat: 프로젝트를 공개할 수 있게 하고, 홈이 무엇을 앞에 둘지 고를 수 있게 한다
공개 화면 다섯 곳이 조용히 비어 있었다. 원인은 하나씩 달랐지만 모두 "값을 채울 방법이 없었다"는 같은 모양이었다. 홈의 "지금 집중하는 것" — `home_focus_config` 는 마이그레이션이 빈 행 하나만 넣어 두었고, 계약에 선언된 `getHomeFocus`/`updateHomeFocus` 는 구현이 없었다. 세 슬롯이 모두 비면 홈은 그 영역을 아예 그리지 않으므로, 운영에서는 한 번도 나타난 적이 없다. Studio 대시보드에 고르는 화면을 둔다. 홈의 "최근 기록" — 화면이 공개된 프로젝트를 하나씩 돌며 타임라인을 조립했다. 그래서 게시한 문서라도 그 프로젝트가 공개되어 있지 않으면 목록에서 통째로 빠졌고, 실제로 릴리스 한 줄만 남았다. 무엇이 최근인지는 공개 투영이 이미 알고 있으므로 그것을 그대로 읽는다. 프로젝트마다 요청을 보내던 N+1 도 사라진다. 프로젝트 공개 — 프로젝트는 `RecordKind` 에 없어 문서 게시 파이프라인을 타지 못하는데, 공개 화면들(프로젝트 목록·프로필의 "현재 프로젝트"·홈 focus)은 전부 `public_resource_projection` 의 PROJECT 행을 가시성 관문으로 쓴다. 그 행을 세우는 경로가 없었으므로 프로젝트는 영원히 비공개였다. 계약에 이미 있던 `publishProject`/`unpublishProject` 를 구현하고 주제·프로젝트 화면에 버튼을 둔다. 문서 사이 관계 연결 — `JdbcCatalogQueryAdapter` 의 RELATION/EVIDENCE 가 `List.of()` 스텁이라 어떤 기록도 연결 대상 목록을 채울 수 없었다. RELATION 은 작성 중에 고르는 것이므로 작업본까지 포함하고, EVIDENCE 는 읽는 사람이 따라갈 수 있어야 하므로 공개된 것만 포함한다. 본문 너비 — 문서 한 편이 세 폭으로 갈라져 있었다. 머리말 920px, 유형·프로젝트 줄은 shell 전체 1180px, 본문은 672px 를 가운데 정렬. 셋을 같은 폭·같은 왼쪽 끝에 세우고 읽는 단을 56rem 으로 넓힌다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XEHXspz4rv5pB5wiiSsVDu
This commit is contained in:
co-authored by
Claude Opus 5
parent
c03b0c77b8
commit
b3aa304975
@@ -11,6 +11,7 @@ import {
|
||||
type HomeFocusItem,
|
||||
} from "./public-content.ts";
|
||||
import type {
|
||||
LatestRecordEntry,
|
||||
PublicContentQueries,
|
||||
PublicTopic,
|
||||
} from "../../application/ports/public-content-queries.ts";
|
||||
@@ -119,6 +120,43 @@ export function listTopics(): PublicTopic[] {
|
||||
.map((entry) => Object.freeze(entry));
|
||||
}
|
||||
|
||||
/**
|
||||
* 픽스처판 "최근 기록". HTTP 어댑터가 서버에서 읽어 오는 것과 같은 의미를 픽스처에서 만든다 —
|
||||
* 프로젝트 활동과 릴리스가 원천이다.
|
||||
*/
|
||||
export function getLatestEntries(): LatestRecordEntry[] {
|
||||
const recordByPath = new Map(publicRecords.map((record) => [record.path, record]));
|
||||
const activities = projects.flatMap((project) =>
|
||||
project.activity.map((activity) => {
|
||||
const record = recordByPath.get(activity.recordPath ?? activity.path);
|
||||
const published = activity.type === "PUBLICATION" && record;
|
||||
return {
|
||||
id: activity.id,
|
||||
entryType: (published ? record.kind : "PROJECT_ACTIVITY") as LatestRecordEntry["entryType"],
|
||||
title: published ? record.title : activity.title,
|
||||
summary: activity.summary,
|
||||
path: activity.path,
|
||||
publishedAt: activity.dateTime,
|
||||
topic: record?.topic ?? project.topics[0] ?? "",
|
||||
project: project.title,
|
||||
};
|
||||
}),
|
||||
);
|
||||
const releaseEntries = releases.map((release) => ({
|
||||
id: `release-${release.version}`,
|
||||
entryType: "RELEASE" as const,
|
||||
title: release.title,
|
||||
summary: release.summary,
|
||||
path: release.path,
|
||||
publishedAt: release.publishedAt,
|
||||
topic: "TechLog",
|
||||
project: "TechLog",
|
||||
}));
|
||||
return [...activities, ...releaseEntries].sort((left, right) =>
|
||||
right.publishedAt.localeCompare(left.publishedAt),
|
||||
);
|
||||
}
|
||||
|
||||
export function getHomeFocusItems(): HomeFocusItem[] {
|
||||
const project = getProject("backend-skeleton");
|
||||
const question = getRecord("QUESTION", "validate-edge-token-again");
|
||||
@@ -264,6 +302,9 @@ export const publicContentQueries = Object.freeze({
|
||||
async listTopics() {
|
||||
return listTopics();
|
||||
},
|
||||
async getLatestEntries() {
|
||||
return getLatestEntries();
|
||||
},
|
||||
async getHomeFocusItems() {
|
||||
return getHomeFocusItems();
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user