fix: 편집과 미리보기를 한 화면에서 보고, 저장·게시를 아래에 고정한다

편집기는 탭이었다. 고친 것이 어떻게 보이는지 확인하려면 편집하던 자리를 화면에서
치워야 했고, 돌아오면 스크롤 위치도 잃었다. 두 패널을 나란히 두면 그 왕복이 통째로
없어진다.

작업 상태와 저장·게시는 오른쪽 세로 rail 이었다. 그 자리는 편집기와 미리보기가 함께
쓸 가로 폭을 가져갔고, 편집 칸이 길어질수록 rail 은 위에 붙은 채 본문만 멀어졌다.
화면 아래에 고정하면 폭을 돌려주면서 스크롤 위치와 무관하게 손이 닿는다.

탭을 없앴으므로 각 패널이 스스로 이름을 가져야 한다. `aria-labelledby` 로 제목을
가리켜 landmark 로 만든다 — 탭 목록이 하던 "여기는 편집, 저기는 미리보기" 안내를
대신한다. `aside` 와 「작업 상태」라는 이름은 그대로 둔다. 자리가 바뀐 것이지 이
묶음이 무엇인지가 바뀐 것은 아니다.

미리보기가 늘 떠 있게 되면서 Picker 테스트의 단언도 함께 고쳤다. 「Picker 가
좁아졌는가」를 화면 전체에 묻고 있었는데, 이제는 미리보기 쪽의 같은 Asset 까지
세게 된다. 그 김에 `.studio-asset-panel` 의 `aria-labelledby` 가 role 없는 div 에서
아무 이름도 만들지 못하던 것도 `role="group"` 으로 실효화했다.

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 15:49:18 +09:00
co-authored by Claude Opus 5
parent b3119952d5
commit 60c8c82097
10 changed files with 197 additions and 169 deletions
@@ -461,8 +461,7 @@ test("inserts the directive at the saved cursor position and the live preview re
`${body.slice(0, cursor)}\n\n${expectedDirective}${body.slice(cursor)}`,
);
await user.click(screen.getByRole("tab", { name: "즉시 미리보기" }));
const panel = screen.getByRole("tabpanel", { name: "즉시 미리보기" });
const panel = screen.getByRole("region", { name: "즉시 미리보기" });
expect(within(panel).queryByRole("alert")).not.toBeInTheDocument();
// `zoom: true` (DIAGRAM kind) renders the figure's image twice -- once as
@@ -1819,12 +1818,12 @@ test("a Picker search never drops an already-inserted asset out of Instant Previ
// must narrow; the preview must not.
await user.type(screen.getByLabelText("Asset 검색"), "other");
await user.click(screen.getByRole("button", { name: "검색" }));
const picker = screen.getByRole("group", { name: "본문에 Asset 삽입" });
await waitFor(() =>
expect(screen.queryByRole("button", { name: /inserted-diagram/ })).not.toBeInTheDocument(),
expect(within(picker).queryByRole("button", { name: /inserted-diagram/ })).not.toBeInTheDocument(),
);
await user.click(screen.getByRole("tab", { name: "즉시 미리보기" }));
const panel = screen.getByRole("tabpanel", { name: "즉시 미리보기" });
const panel = screen.getByRole("region", { name: "즉시 미리보기" });
assert.equal(
within(panel).queryByRole("alert")?.textContent ?? null,
@@ -166,8 +166,7 @@ describe("TechLog Studio project decision authoring", () => {
expect(screen.getByRole("complementary", { name: "작업 상태" }))
.toHaveTextContent("종류Decision");
await user.click(screen.getByRole("tab", { name: "즉시 미리보기" }));
const preview = screen.getByRole("tabpanel", { name: "즉시 미리보기" });
const preview = screen.getByRole("region", { name: "즉시 미리보기" });
expect(
within(preview).getByRole("heading", {
name: "목록 페이징과 컬렉션 로딩을 분리합니다",
@@ -114,20 +114,17 @@ describe("TechLog Studio document editor", () => {
]);
expect(within(rail).queryAllByRole("link")).toHaveLength(0);
const editTab = screen.getByRole("tab", { name: "편집" });
const previewTab = screen.getByRole("tab", { name: "즉시 미리보기" });
editTab.focus();
await user.keyboard("{ArrowRight}");
expect(previewTab).toHaveFocus();
expect(previewTab).toHaveAttribute("aria-selected", "true");
// 편집과 미리보기는 한 화면에 함께 있다. 탭이었을 때는 고친 결과를 보려면 편집하던 자리를
// 화면에서 치워야 했고, 돌아오면 스크롤 위치도 잃었다 — 그 왕복이 없어야 한다는 것이
// 이 화면의 요구다. 아무것도 누르지 않은 채로 둘 다 보이는지 묻는다.
expect(screen.getByRole("region", { name: "문서 편집" })).toBeVisible();
expect(
within(screen.getByRole("tabpanel", { name: "즉시 미리보기" })).getByRole(
within(screen.getByRole("region", { name: "즉시 미리보기" })).getByRole(
"heading",
{ level: 1, name: "편집한 Redis 경계" },
),
).toBeVisible();
await user.keyboard("{Home}");
expect(editTab).toHaveFocus();
expect(screen.queryByRole("tab", { name: "즉시 미리보기" })).toBeNull();
expect(screen.getByLabelText("제목")).toHaveValue("편집한 Redis 경계");
expect(calls).toEqual({ save: 0, validate: 0, preview: 0, publish: 0 });
});
@@ -105,7 +105,8 @@ async function interact(page, action) {
} else if (action === "studio-menu") {
await page.getByRole("button", { name: "Studio 메뉴 열기" }).click();
} else if (action === "immediate-preview") {
await page.getByRole("tab", { name: "즉시 미리보기" }).click();
// 미리보기는 이제 편집 옆에 늘 떠 있다 — 열 탭이 없으므로 자리 잡기를 기다리기만 한다.
await page.getByRole("region", { name: "즉시 미리보기" }).waitFor();
} else if (action === "dirty-dialog") {
await page.getByLabel("요약").fill("저장하지 않은 시각 검증 변경");
await page.getByRole("link", { name: "게시 기록", exact: true }).first().click();
+1 -2
View File
@@ -95,8 +95,7 @@ test("Studio immediate preview matches the source without a mask", async ({ page
page,
"/studio/documents/11111111-1111-4111-8111-111111111111/edit",
);
await page.getByRole("tab", { name: "즉시 미리보기" }).click();
await expect(page.getByRole("tabpanel", { name: "즉시 미리보기" })).toBeVisible();
await expect(page.getByRole("region", { name: "즉시 미리보기" })).toBeVisible();
await expect(page).toHaveScreenshot("tech-log-studio-immediate-preview-1440.png", {
fullPage: true,
});