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
@@ -361,7 +361,11 @@ describe("TechLog release and profile screens", () => {
).toEqual(["/projects/backend-skeleton", "/projects/auth-lab"]);
expect(
Array.from(container.querySelectorAll(".profile-topics li"), (item) => item.textContent),
).toEqual(["Backend Architecture", "JPA", "Authentication", "Redis"]);
// Derived from the catalogue, not a literal in the page. The old hard-coded
// list opened with "Backend Architecture", which no record in the fixture
// actually carries — the profile was advertising a topic that did not
// exist, and nothing could have caught it while the list lived in the JSX.
).toEqual(["JPA", "Authentication", "Redis"]);
expect(within(main).queryByText(/이메일|연락처|경력|소속|회사/)).not.toBeInTheDocument();
expect(container.querySelector('a[href^="mailto:"]')).toBeNull();
});