feat(804-8): M02 단가산출 수식 탭 = M01 과 같은 단추 · 저장 한 번 · 머리 단추 줄바꿈 막음(요구 689)

- 공용 줄 쌓기 화면: 페이지가 저장을 맡는 꼴(`page`)을 M02 단가산출 화면에도 — 탭 위 [고침 버림] · [수식 저장] 걷음 · M02 사본 [단가 갱신] · [재생성] · 「M01 새 판」 배지는 그대로 · 「줄 쌓기에서 못 고치는 칸」 안내 줄 걷음(금액 줄 기호 = 율 줄이 앞 줄 금액을 부르는 속 이름 · 화면에서 고칠 값 아님 · 보존 그대로)
- M02 머리 단추 = [초기화] · [삭제] · [되돌림] · [저장] — 단가산출 상세는 [새로고침] 숨김 · [되돌림] · [저장] = 고침 있을 때만(기본정보 + 수식) · [저장] 한 번 = 수식 먼저 저장(새 판 · 수식 칸 받음) → 단가산출 문서 저장 · [되돌림] · 버리고 떠남 = 수식 캐시도 비움
- 보기 고르기(수식 · 단가산출 상세) = M01 처럼 머리 줄 · [항목 추가] · [율 줄 추가] 걷음(줄 더하기 = 수식 탭 [+] 단가산출 · 일위대가 복사) · 일위대가 줄 모달 안 상세는 그대로
- 머리 단추 줄 맞춤(부품 상자 `fitButtonRow`): 단추가 아래로 떨어지거나 제목이 여러 줄이면 아이콘 단추는 아이콘만(글 = 풍선 도움말) · M01 단추 다섯 · M02 단추 넷 아이콘 · 단가산출 상세 긴 이름은 제목 안에서 줄바꿈
- 사본 할일 막힘 알림 글 = [저장] 또는 [되돌림] 먼저
- 시험: test_804_7 에 M02 단추 · 저장 한 번 · 줄 맞춤 지킴 더함 · test_804_4 권한 시험에 수식 저장 판 = 페이지 저장 판 이어 씀 확인 더함

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-08 14:56:31 +09:00
co-authored by Claude Opus 5.5
parent ef5fb0632f
commit eb64bf64ac
16 changed files with 209 additions and 53 deletions
@@ -182,6 +182,9 @@ def test_권한_저장_단가갱신(world, monkeypatch):
# 페이지 [저장](옛 칸 · 이름) 뒤에도 고친 문서 그대로(빈 SK 씨로 덮지 않음)
with store.at(me):
file = store.read("sangeun", target)
assert (
file["판"] == saved["판"]
) # 804-8 페이지 [저장] 한 번 = 수식 저장 판 그대로 이어 씀(409 없음)
store.write("sangeun", target, file["판"], {**file["문서"], "이름": "sub3임시 이름바꿈"})
again = _norm(path.read(target))
assert again["셈"]["계"] == saved["셈"]["계"] and again["사본"]["따로"]
@@ -203,3 +203,38 @@ def test_시험_페이지_걷음_화면_차림() -> None:
assert "html:not" in _src("M01_MasterData/M01_MasterData_UI_LogicLab_Side.css")
for f in (ROOT / "A00_Common").glob("line_stack*.ts"):
assert len(f.read_text(encoding="utf-8").splitlines()) <= 700, f.name
def test_M02_수식탭_M01과_같은_단추_저장_한_번() -> None:
"""804-8 — M02 단가산출 수식 탭 = M01 과 같은 공용 화면 · 페이지 [되돌림] · [저장] 한 번 · 머리 줄 한 줄."""
screen = _src("A00_Common/line_stack_screen.ts")
assert "...(cp && edit() && key ? copyTools(true, (job) => void runJob(job)) : [])," in screen
assert "notesOf" not in screen and "lstk__notes" not in screen # 못 고치는 칸 안내 줄 걷음
assert "Notes:" not in _src("A00_Common/line_stack_text.ts")
tabs = _src("M02_MasterTemplete/M02_MasterTemplete_Sangeun_Tabs.ts")
assert "page: opt.page," in tabs and "save: () => stack.save()," in tabs
detail = _src("M02_MasterTemplete/M02_MasterTemplete_Sangeun_Detail.ts")
assert "page: opt.source ? () => report() : undefined," in detail
assert "JSON.stringify(doc) !== saved || tabs.dirty()" in detail # 고침 알림 = 기본 + 수식
assert "addButton.hidden = !edit || !!opt.source;" in detail # [항목 추가] · [율 줄 추가] 걷음
assert "rateButton.hidden = !edit || !!opt.source;" in detail
assert "if (!(await tabs.save())) throw" in detail and "await refreshing;" in detail
assert "if (tabs.dirty()) void tabs.discard();" in detail # 버리고 떠남 = 수식 캐시도
view = _src("M02_MasterTemplete/M02_MasterTemplete_Sangeun_View.ts")
assert "getDoc: () => one.flush().then(() => one.getDoc())" in view
acts = _src("M02_MasterTemplete/M02_MasterTemplete_Main_Actions.ts")
assert (
'Undo: ["되돌림", "Revert"],' in acts and "ctx.page.save.disabled = sk() && !dirty;" in acts
)
assert "if (sk()) ctx.page.reload.hidden = true;" in acts # 단가산출 상세 = [새로고침] 숨김
main = _src("M02_MasterTemplete/M02_MasterTemplete_UI_Main.ts")
assert "actions.undo,\n save," in main and "actions.dim((dirty = false));" in main
assert "onChanged, onVersion, slot }" in main # 보기 고르기 = 머리 줄
assert "fitButtonRow(" in main and "fitButtonRow(root);" in _src(
"M01_MasterData/M01_MasterData_UI_Head.ts"
)
lab = _src("M01_MasterData/M01_MasterData_UI_LogicLab.ts")
for i in ('"check"', '"undo"', '"copy"', '"plus"', '"trash"'):
assert f"iconStart: {i}" in lab, i
css = _src("ui_template/ui_template_button.css")
assert ".ui-btn-row--tight .ui-btn:has(> .ui-btn__icon) > .ui-btn__label" in css