fix(772-1): ☰ 서랍 진행 단계 · 바로가기 칸 빠짐 — 진행 판 공용 자리에서 맡김 · 안 맡긴 쪽 기본 목록

- 까닭: B03 파일입력 = 진행 판을 직접 조립(createWorkflowOverlays)해 서랍 맡김(setDrawerSteps)이 빠짐 ·
  B02 · B10 · B11 · D01 · E01 = 일반 판이라 맡기는 쪽이 없음 · B01 = 10-26 새 틀(e505f4014)에서 옛 ☰ 구역 바로가기를 걷음
- 진행 판(createWorkflowOverlays)이 progressContent 를 받으면 서랍에 맡김 — 워크플로 판 · B03 한 자리
- ☰ 를 열 때 맡긴 목록이 없으면 기본 목록 = 🏠 대시보드 + 권한별 M01 · M02(mountSettingsDrawer 와 같은 한 벌) · 늦게 온 쪽 목록이 이김
- 터치 켬 서랍 진행 단계 줄 40 → 44(옆 규칙이 더 세어 덮던 것)
- 시험: helper_280_2 의 app_shell 대역은 workflow_layout 이 더는 안 불러 지움 · helper_359_2 에 overlay 용 대역 더함 · test_772_1 새로

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-03 20:05:47 +09:00
co-authored by Claude Opus 5.5
parent ee5125f012
commit f96907fb8a
7 changed files with 74 additions and 10 deletions
+12 -3
View File
@@ -133,7 +133,7 @@ function buildBrand(): HTMLButtonElement {
* 서랍을 열 때 서랍으로 옮기고 닫을 때 제자리(진행 판)로 — 그리기 · 누름 코드는 한 벌. */
let drawerSteps: { node: HTMLElement; home: HTMLElement | null } | null = null;
/** 집(진행 판) 없는 요소 = 서랍이 늘 가짐 · 닫을 때 안 옮김(B01 · M01 · M02 바로가기) */
/** 집(진행 판) 없는 요소 = 서랍이 늘 가짐 · 닫을 때 안 옮김(M01 · M02 바로가기 · 기본 목록) */
export function setDrawerSteps(node: HTMLElement | null): void {
drawerSteps = node ? { node, home: node.parentElement } : null;
}
@@ -178,11 +178,20 @@ function buildDrawer(header: HTMLElement): HTMLElement[] {
const setOpen = (open: boolean): void => {
header.classList.toggle("is-drawer-open", open);
burger.setAttribute("aria-expanded", String(open));
if (open && !drawerSteps) void fillDefaultSteps();
if (!drawerSteps) return;
if (open) steps.append(drawerSteps.node);
if (open)
steps.replaceChildren(drawerSteps.node); // 앞서 맡은 기본 목록이 있으면 갈아 끼움
else if (drawerSteps.home && steps.contains(drawerSteps.node))
drawerSteps.home.append(drawerSteps.node);
};
// 진행 판 · 바로가기를 안 맡긴 쪽(772-1) = 기본 목록(🏠 대시보드 + 권한별 M01 · M02) · 늦게 온 쪽 목록이 이김
const fillDefaultSteps = async (): Promise<void> => {
const bar = await import("./m_settings_nav").then((m) => m.defaultSettingsBar());
if (!bar || drawerSteps || !steps.isConnected) return;
setDrawerSteps(bar);
if (header.classList.contains("is-drawer-open")) steps.replaceChildren(bar);
};
burger.addEventListener("click", () => setOpen(!header.classList.contains("is-drawer-open")));
scrim.addEventListener("click", () => setOpen(false));
header.addEventListener("keydown", (e) => e.key === "Escape" && setOpen(false)); // ☰ 에 포커스가 있어도 닫힘
@@ -485,7 +494,7 @@ html:is([data-burger="on"], .is-bm-route) .app-burger { display: inline-flex; }
.app-drawer__steps .ui-workflow-layout__steps.is-vertical .ui-workflow-layout__step { min-height: var(--spacing-40); margin-inline: 8px; width: auto; padding-left: 7px; }
.app-drawer :is(.ui-btn__label, .ui-workflow-layout__step-label) { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.app-drawer__steps .ui-workflow-layout__step--home { margin-bottom: 0; }
html[data-touch="on"] :is(.app-drawer__nav > *, .app-drawer__site > summary, .app-drawer__steps .ui-workflow-layout__step, .app-drawer__row > *) { min-height: 44px; }
html[data-touch="on"] :is(.app-drawer__nav > *, .app-drawer__site > summary, .app-drawer__steps .ui-workflow-layout__steps.is-vertical .ui-workflow-layout__step, .app-drawer__row > *) { min-height: 44px; }
.app-drawer__row {
display: flex;
flex-wrap: wrap;
+19 -3
View File
@@ -1,7 +1,8 @@
/* =============================================================================
* m_settings_nav.ts
* 시스템 설정 바로가기(334 · 요구 225-2 · 716-2) — 대시보드 설계 기본값 줄(`settingLinks` · 칸마다 한 줄 ·
* `?open=`) / M01 · M02 ☰ 메뉴(`pageLinks` · 페이지 둘 · `mountSettingsDrawer`).
* `?open=`) / M01 · M02 ☰ 메뉴(`pageLinks` · 페이지 둘 · `mountSettingsDrawer`) /
* 진행 판 없는 쪽 ☰ 기본 목록(`defaultSettingsBar` · 772-1).
* ========================================================================== */
import { ROUTES, type RoutePath } from "@config/config_frontend";
@@ -112,8 +113,24 @@ export function mountSettingsDrawer(
activeKey: string,
companyId: number | string | null,
): void {
setDrawerSteps(settingsBar(user, activeKey, companyId));
}
/** ☰ 기본 목록(772-1) — 진행 판 · 바로가기를 안 세운 로그인 뒤 쪽(B02 · D01 · E01 등) = 🏠 대시보드 + 권한별 페이지 둘 ·
* 로그인 전 = 없음 */
export async function defaultSettingsBar(): Promise<HTMLElement | null> {
const { fetchSessionUser } = await import("../A06_Login/A06_Login_Api_Fetch");
const user = await fetchSessionUser().catch(() => null);
return user ? settingsBar(user, "", null) : null;
}
function settingsBar(
user: SettingUser,
activeKey: string,
companyId: number | string | null,
): HTMLElement {
const links = pageLinks(user, companyId);
const bar = createStepBar(
return createStepBar(
links.map((link) => link.title),
links.findIndex((link) => link.key === activeKey),
{
@@ -124,7 +141,6 @@ export function mountSettingsDrawer(
onStepClick: (index) => void go(links[index]),
},
);
setDrawerSteps(bar);
}
async function go(link: SettingLink): Promise<void> {
@@ -156,7 +156,6 @@ const STUBS = {
},
},
"../A00_Common/router": { navigateTo: () => undefined },
"../A00_Common/app_shell": { setDrawerSteps: () => undefined },
"../B01_Dashboard/B01_Dashboard_Api_Fetch": { fetchDashboardMe: async () => ({ role }) },
};
globalThis.__DEV = false;
@@ -107,6 +107,7 @@ const { loadTs } = makeLoader({
"/b_workflow_nav": { fetchWorkflowState: async () => ({}) },
"/b_page_state": { readByKey: () => null, writeByKey: () => undefined },
"/device_context": { SIZE_BREAKS: { expanded: 840 } },
"/app_shell": { setDrawerSteps: () => undefined },
},
});
const overlay = loadTs(path.join(ROOT, "ui_template", "ui_template_overlay.ts"));
@@ -0,0 +1,39 @@
"""772-1 ☰ 서랍 진행 단계 · 바로가기 칸 — 진행 판을 세우는 공용 자리 한 곳에서 맡김 · 안 맡긴 쪽은 기본 목록."""
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
def _text(rel: str) -> str:
return (ROOT / rel).read_text(encoding="utf-8")
def test_진행_판을_세우면_서랍에_맡김():
overlay = _text("ui_template/ui_template_overlay.ts")
body = overlay.split("export function createWorkflowOverlays")[1]
assert (
"setDrawerSteps(options.progressContent)" in body.split("if (options.progressContent)")[1]
)
# 워크플로 판 · B03 은 진행 판(overlay)에만 맡김 — 따로 부르면 두 번
assert "setDrawerSteps" not in _text("ui_template/ui_template_workflow_layout.ts")
assert "setDrawerSteps" not in _text("B03_FileInput/B03_FileInput_UI_Page.ts")
assert "createWorkflowOverlays" in _text("B03_FileInput/B03_FileInput_UI_Page.ts")
def test_안_맡긴_쪽은_기본_목록():
shell = _text("A00_Common/app_shell.ts")
set_open = shell.split("const setOpen")[1].split("};")[0]
assert "if (open && !drawerSteps) void fillDefaultSteps();" in set_open
assert "steps.replaceChildren(drawerSteps.node)" in set_open
fill = shell.split("const fillDefaultSteps")[1].split("};")[0]
assert (
"defaultSettingsBar()" in fill
and "if (!bar || drawerSteps || !steps.isConnected) return;" in fill
)
nav = _text("A00_Common/m_settings_nav.ts")
default = nav.split("export async function defaultSettingsBar")[1].split("\n}")[0]
assert "fetchSessionUser" in default and 'settingsBar(user, "", null)' in default
# M01 · M02 와 같은 한 벌(pageLinks · createStepBar · 🏠)
bar = nav.split("function settingsBar")[1]
assert "pageLinks(user, companyId)" in bar and "homeButton: true" in bar
+3
View File
@@ -22,6 +22,7 @@ import { fetchWorkflowState } from "../A00_Common/b_workflow_nav";
import { currentProjectId } from "../A00_Common/b_project_switch";
import { readByKey, writeByKey } from "../A00_Common/b_page_state";
import { SIZE_BREAKS } from "../A00_Common/device_context";
import { setDrawerSteps } from "../A00_Common/app_shell";
const TITLE_OVERLAY_STATE_KEY = "frd_workflow_title_overlay_open";
const PROGRESS_OVERLAY_STATE_KEY = "frd_workflow_progress_overlay_open";
@@ -294,6 +295,8 @@ export function createWorkflowOverlays(options: WorkflowOverlayOptions): Workflo
if (options.progressContent) {
const progressBody = document.createElement("div");
progressBody.append(options.progressContent);
// ☰ 서랍 진행 단계 묶음(6-2 · 772-1) — 진행 판을 세우는 모든 화면(워크플로 판 · B03)이 같은 목록을 서랍에 빌려 줌
setDrawerSteps(options.progressContent);
const progressPanel = createPanel(
"progress",
t("Workflow_Progress_Title"),
@@ -14,7 +14,6 @@ import { t } from "./ui_template_locale";
import { createWorkflowOverlays } from "./ui_template_overlay";
import { showToast } from "./ui_template_elements";
import { navigateTo } from "../A00_Common/router";
import { setDrawerSteps } from "../A00_Common/app_shell";
import { fetchDashboardMe } from "../B01_Dashboard/B01_Dashboard_Api_Fetch";
let dashboardRolePromise: Promise<string> | undefined;
@@ -243,8 +242,6 @@ export function createWorkflowLayout(options: WorkflowLayoutOptions): WorkflowLa
});
root.append(body, overlays.root);
// ☰ 서랍 진행 단계 묶음(6-2) — 같은 단계 목록을 서랍이 열릴 때 빌려 감(그리기 · 누름 한 벌)
setDrawerSteps(progressContent);
const steps = [
...progressContent.querySelectorAll<HTMLElement>(
".ui-workflow-layout__step:not(.ui-workflow-layout__step--home)",