feat: read the profile's topics from Studio, and add working-copy deletion

Two things an author could not control from Studio.

The profile's "주요 관심 주제" was four strings in the JSX. Creating or removing
a topic in Studio changed nothing, and correcting the list meant a rebuild and
a redeploy. It now renders the published topic list. The old literal opened
with "Backend Architecture", which no record in the catalogue actually carries
— the profile was advertising a topic that did not exist, and nothing could
have caught that while the list lived in the markup.

The working-copy list gained a delete control. It routes by kind because the
contract and the storage both do: Case and Reference share one table split by
type, Question is its own. Decision has no delete — its lifecycle is accept,
reject, supersede, which records what happened rather than erasing it — so the
control does not appear for it.

The list summary carries no version, so deletion reads the working copy first
and uses the version it finds. A stale version from a list left open should
fail as a conflict, not delete whatever is there now.
This commit is contained in:
DongHyeonka
2026-08-21 13:30:37 +09:00
parent ab8c6c14db
commit c5e8735041
15 changed files with 296 additions and 92 deletions
@@ -23,8 +23,6 @@ const principles = [
},
] as const;
const topics = ["Backend Architecture", "JPA", "Authentication", "Redis"] as const;
export function ProfilePage() {
// The two project slugs this named were the static fixture's, and they exist
// in no real deployment — the page asked the backend for them, took two 404s,
@@ -34,10 +32,14 @@ export function ProfilePage() {
const entries = (await queries.searchPublicContent("")).filter(
(item) => item.contentType === "PROJECT",
);
const resolved = await Promise.all(
entries.map((item) => queries.getProject(item.path.replace("/projects/", ""))),
);
return { currentProjects: resolved.filter((project) => project !== undefined) };
const [resolved, topics] = await Promise.all([
Promise.all(entries.map((item) => queries.getProject(item.path.replace("/projects/", "")))),
queries.listTopics(),
]);
return {
currentProjects: resolved.filter((project) => project !== undefined),
topics,
};
});
// Only the project list comes from the network. Returning the page-wide
@@ -96,14 +98,24 @@ export function ProfilePage() {
</ul>
)}
</section>
{/*
이 목록은 코드에 네 개가 박혀 있었다 — Studio 에서 주제를 만들거나 지워도 프로필은
그대로였고, 고치려면 배포를 다시 해야 했다. 이제 공개 주제 목록을 그대로 그린다.
*/}
<section className="profile-topics" aria-labelledby="profile-topics-title">
<p className="section-kicker">Topics</p>
<h2 id="profile-topics-title"> </h2>
<ul>
{topics.map((topic) => (
<li key={topic}>{topic}</li>
))}
</ul>
{!view.ready ? (
view.fallback
) : view.data.topics.length === 0 ? (
<p className="public-empty-note"> .</p>
) : (
<ul>
{view.data.topics.map((topic) => (
<li key={topic.slug}>{topic.name}</li>
))}
</ul>
)}
</section>
</main>
);