diff --git a/app/game-api/test/nationHtml.test.ts b/app/game-api/test/nationHtml.test.ts
index 2dbd1f73..6dae4b9c 100644
--- a/app/game-api/test/nationHtml.test.ts
+++ b/app/game-api/test/nationHtml.test.ts
@@ -73,7 +73,7 @@ describe('nation HTML purification', () => {
const source = [
'
',
'방침',
- '
',
+ '
',
].join('');
const clean = purifyNationHtml(source);
@@ -83,6 +83,7 @@ describe('nation HTML purification', () => {
'style="font-family:Pretendard, sans-serif;font-size:22px;color:#123456;background-color:#fedcba"'
);
expect(clean).toContain('
');
+ expect(clean).toContain('class="custom-image-align-right"');
expect(clean).toContain(
'src="https://sam-image.hided.net/uploads/core2026/0123456789abcdef0123456789abcdef.webp"'
);
diff --git a/app/game-frontend/e2e/nationOffices.spec.ts b/app/game-frontend/e2e/nationOffices.spec.ts
index 9b0c8865..acf920c4 100644
--- a/app/game-frontend/e2e/nationOffices.spec.ts
+++ b/app/game-frontend/e2e/nationOffices.spec.ts
@@ -624,6 +624,21 @@ test('finance editor preserves Ref formatting controls and uploads images throug
expect(await editorFrame.evaluate((element) => getComputedStyle(element).backgroundColor)).toBe('rgba(0, 0, 0, 0)');
expect(await editor.evaluate((element) => getComputedStyle(element).backgroundColor)).toBe('rgba(0, 0, 0, 0)');
+ await editor.fill('첫 번째 항목');
+ await page.getByRole('button', { name: '번호 목록' }).click();
+ await expect(editor.locator('ol')).toHaveCSS('list-style-type', 'decimal');
+ await expect(editor.locator('li')).toContainText('첫 번째 항목');
+ await screenshot(page, 'core-finance-numbered-list-desktop.png');
+ await page.getByRole('button', { name: '번호 목록' }).click();
+
+ const alignmentIcons = await page
+ .getByRole('toolbar', { name: '서식' })
+ .getByRole('button', { name: /^(왼쪽|가운데|오른쪽) 정렬$/ })
+ .locator('svg')
+ .evaluateAll((icons) => icons.map((icon) => icon.innerHTML));
+ expect(alignmentIcons).toHaveLength(3);
+ expect(new Set(alignmentIcons).size).toBe(3);
+
await editor.fill('서식 검증');
await editor.press('Control+A');
await page.getByRole('combobox', { name: '글꼴', exact: true }).selectOption('Gungsuh, serif');
@@ -661,11 +676,27 @@ test('finance editor preserves Ref formatting controls and uploads images throug
await expect.poll(() => state.uploadDataUrl).toMatch(/^data:image\/png;base64,/);
await expect(editor.locator('hr')).toHaveCount(1);
- await expect(editor.locator('img')).toHaveAttribute(
+ const uploadedImage = editor.locator('img');
+ await expect(uploadedImage).toHaveAttribute(
'src',
'https://sam-image.hided.net/uploads/core2026/0123456789abcdef0123456789abcdef.webp'
);
+ await uploadedImage.click();
+ const imageToolbar = page.getByRole('toolbar', { name: '이미지 정렬' });
+ await expect(imageToolbar).toBeVisible();
+ await expect(imageToolbar.getByRole('button')).toHaveCount(5);
+ await screenshot(page, 'core-finance-image-toolbar-desktop.png');
+ await imageToolbar.getByRole('button', { name: '이미지 오른쪽 정렬' }).click();
+ await expect(uploadedImage).toHaveClass(/custom-image-align-right/);
+ await expect(uploadedImage).toHaveCSS('display', 'block');
+ await expect(uploadedImage).toHaveCSS('margin-right', '0px');
+
+ await page.getByRole('toolbar', { name: '서식' }).getByRole('button', { name: '가운데 정렬' }).click();
+ await expect(uploadedImage).toHaveClass(/custom-image-align-center/);
+ await uploadedImage.click();
+ await imageToolbar.getByRole('button', { name: '이미지 오른쪽 정렬' }).click();
+
const imageButton = page.getByRole('button', { name: '이미지', exact: true });
await imageButton.hover();
await expect(imageButton).toHaveCSS('border-color', 'rgb(157, 200, 240)');
@@ -691,11 +722,54 @@ test('finance editor preserves Ref formatting controls and uploads images throug
expect(state.noticeMutationInput).toContain('background-color: rgb(254, 220, 186)');
expect(state.noticeMutationInput).toContain('text-align: center');
expect(state.noticeMutationInput).toContain('
');
+ expect(state.noticeMutationInput).toContain('class="custom-image-align-right"');
expect(state.noticeMutationInput).toContain(
'https://sam-image.hided.net/uploads/core2026/0123456789abcdef0123456789abcdef.webp'
);
});
+test('recruitment editor keeps the Ref 870px content width in desktop and scales it to 500px on mobile', async ({
+ page,
+}) => {
+ await installFixture(page, { role: 'head', rate: 20 });
+ await page.setViewportSize({ width: 1000, height: 900 });
+ await gotoOffice(page, 'nation/finance');
+ await page.getByRole('button', { name: '임관 권유문 수정' }).click();
+
+ const form = page.locator('#scout-message-form');
+ const editorFrame = form.locator('.legacy-html-editor');
+ const desktop = await form.evaluate((element) => {
+ const frame = element.querySelector('.legacy-html-editor')!;
+ const formRect = element.getBoundingClientRect();
+ const frameRect = frame.getBoundingClientRect();
+ return {
+ formWidth: formRect.width,
+ frameWidth: frameRect.width,
+ leftInset: frameRect.left - formRect.left,
+ rightInset: formRect.right - frameRect.right,
+ };
+ });
+ expect(desktop.formWidth).toBe(1000);
+ expect(desktop.frameWidth).toBe(870);
+ expect(desktop.leftInset).toBe(130);
+ expect(desktop.rightInset).toBe(0);
+
+ await page.setViewportSize({ width: 500, height: 900 });
+ const mobile = await editorFrame.evaluate((element) => {
+ const rect = element.getBoundingClientRect();
+ return {
+ visualWidth: rect.width,
+ sourceWidth: element.clientWidth,
+ toolbarWidth: element.querySelector('[role="toolbar"]')!.getBoundingClientRect().width,
+ };
+ });
+ expect(mobile.sourceWidth).toBe(870);
+ expect(mobile.visualWidth).toBeCloseTo(500, 1);
+ expect(mobile.toolbarWidth).toBeCloseTo(500, 1);
+ expect(await page.locator('.page-finance').evaluate((element) => element.scrollWidth)).toBeLessThanOrEqual(500);
+ await screenshot(page, 'core-finance-scout-editor-mobile.png');
+});
+
test('finance adopts the server-purified notice and scout message before rendering the saved preview', async ({
page,
}) => {
diff --git a/app/game-frontend/src/components/ui/LegacyHtmlEditor.vue b/app/game-frontend/src/components/ui/LegacyHtmlEditor.vue
index 199866e7..9f68d68e 100644
--- a/app/game-frontend/src/components/ui/LegacyHtmlEditor.vue
+++ b/app/game-frontend/src/components/ui/LegacyHtmlEditor.vue
@@ -1,16 +1,17 @@