refactor(472-5 5-B 값 주인 하나): 횡단 상세 객체 = 상세 가게 하나 — 계산 묶음 페이지는 지문(노선 · 표본)만

- 상세 가게 칸 셋(B06_Section_Section_Canon.ts · 가벼운 자리): 저장 정본 상세(받을 때 · 세션 · 판 바뀜 · 되돌릴 때 · 갈아끼움 제외) · 초안 전 hold 상세(판 바뀌면 새 판) · 노선 초안 상세(= pipelineDetail)
- 페이지: canon.detail · held.detail · shown 사본 걷음 — 저장 기준 셈(savedEdits · storedPvi · crossNow)은 가게 칸을 읽음 · 초안 버림 = 가게 hold 상세로 정본 잡음
- 떠남(forgetSectionMemory) · invalidate 때 칸 셋 비움 · savedStations 는 sub2 관 · 구조물 줄로 셈 → 남김
- 시험: test_472_5b_detail_owner(옛 코드 빨강 3) · 묶음 헬퍼 넷(365 · 346_2 · 349_0j · 451_drop)이 종단도 흉내 정본을 가게 칸에도 채움
- 위키 design_page 값 · 규칙 · 층 줄

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-19 10:07:30 +09:00
co-authored by Claude Opus 5.5
parent 3b5022a9c0
commit 77be773c07
14 changed files with 227 additions and 38 deletions
@@ -69,6 +69,8 @@ function loadTs(full) {
return box.exports;
}
const load = (...parts) => loadTs(path.join(ROOT, ...parts));
// 상세 가게 칸(472-5) — 실제는 상세 가게가 받을 때 채움 · 여기선 종단도가 내주는 저장 상세 그대로
const slots = () => load("B06_Section", "B06_Section_Section_Canon.ts");
const sampler = load("common_util", "common_util_surface_sampler.ts");
const bytes = fs.readFileSync(process.argv[3]);
@@ -190,7 +192,7 @@ async function run() {
pipelineSource: () => ({
base: savedBase,
edits: alignment.emptyEdits(),
detail: job.detail,
detail: (slots().keepSavedDetail(P, 7, job.detail), job.detail),
routeId: 7,
}),
adoptPipeline: (base, edits, draft) => adopted.push({ draft, saved: base === savedBase }),
@@ -69,6 +69,8 @@ function loadTs(full) {
return box.exports;
}
const load = (...parts) => loadTs(path.join(ROOT, ...parts));
// 상세 가게 칸(472-5) — 실제는 상세 가게가 받을 때 채움 · 여기선 종단도가 내주는 저장 상세 그대로
const slots = () => load("B06_Section", "B06_Section_Section_Canon.ts");
const sampler = load("common_util", "common_util_surface_sampler.ts");
const bytes = fs.readFileSync(process.argv[3]);
@@ -200,7 +202,7 @@ async function draftRun(P, grid) {
pipelineSource: () => ({
base: savedBase,
edits: alignment.emptyEdits(),
detail: job.detail,
detail: (slots().keepSavedDetail(P, 7, job.detail), job.detail),
routeId: 7,
}),
adoptPipeline: () => undefined,
@@ -68,6 +68,10 @@ function loadTs(full) {
return box.exports;
}
const load = (...parts) => loadTs(path.join(ROOT, ...parts));
// 상세 가게 칸(472-5) — 실제는 상세 가게가 받을 때 채움 · 여기선 종단도가 내주는 저장 상세 그대로
const slots = () => load("B06_Section", "B06_Section_Section_Canon.ts");
/** 이 시험은 묶음 여럿(P · Q · B)이 같은 종단도 흉내를 씀 — 칸 = 지금 연 프로젝트. */
const here = () => window.localStorage.getItem("frd_current_project_id");
const sampler = load("common_util", "common_util_surface_sampler.ts");
const bytes = fs.readFileSync(process.argv[3]);
@@ -214,7 +218,7 @@ async function run() {
pipelineSource: () => ({
base: savedBase,
edits: alignment.emptyEdits(),
detail: job.detail,
detail: (slots().keepSavedDetail(here(), 7, job.detail), job.detail),
routeId: 7,
}),
adoptPipeline: () => undefined,
@@ -222,7 +226,8 @@ async function run() {
});
const options = {
stationIntervalM: job.interval,
holdDraft: (_routeId, saved) => {
holdDraft: (routeId, saved) => {
slots().keepHeldDetail(here(), routeId, saved); // 초안 전 상세 칸(472-5 · 가게 hold 와 같음)
holds.push(saved === job.detail ? "saved" : saved ? "other" : "null");
return async () => "saved";
},
+10 -5
View File
@@ -67,6 +67,8 @@ function loadTs(full) {
return box.exports;
}
const load = (...parts) => loadTs(path.join(ROOT, ...parts));
// 상세 가게 칸(472-5) — 실제는 상세 가게가 받을 때 채움 · 여기선 종단도가 내주는 저장 상세 그대로
const slots = () => load("B06_Section", "B06_Section_Section_Canon.ts");
const sampler = load("common_util", "common_util_surface_sampler.ts");
const bytes = fs.readFileSync(process.argv[3]);
@@ -215,7 +217,7 @@ async function run() {
pipelineSource: () => ({
base: savedBase,
edits: alignment.emptyEdits(),
detail: job.detail,
detail: (slots().keepSavedDetail(P, 7, job.detail), job.detail),
routeId: 7,
}),
adoptPipeline: (_base, _edits, draft) => adopts.push(draft ? "draft" : "saved"),
@@ -223,10 +225,13 @@ async function run() {
},
{
stationIntervalM: job.interval,
holdDraft: () => async (stations) => {
restores.push({ at: adopts.length, stations: stations.length });
return "saved";
},
holdDraft: (routeId, saved) => (
slots().keepHeldDetail(P, routeId, saved), // 초안 전 상세 칸(472-5 · 가게 hold 와 같음)
async (stations) => {
restores.push({ at: adopts.length, stations: stations.length });
return "saved";
}
),
},
);
pageMod.pipelineEarthwork(P, { conversion: job.conversion });
+36
View File
@@ -0,0 +1,36 @@
/* 472-5 5-B 헬퍼 — 상세 가게 칸 셋(`B06_Section_Section_Canon.ts`) 넣고 비움. 짝 = `test_472_5b_detail_owner.py`. */
const fs = require("fs");
const path = require("path");
const ROOT = path.join(__dirname, "..", "..");
const ts = require(path.join(ROOT, "config", "node_modules", "typescript"));
const file = path.join(ROOT, "B06_Section", "B06_Section_Section_Canon.ts");
const js = ts.transpileModule(fs.readFileSync(file, "utf8"), {
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
}).outputText;
const box = { exports: {} };
new Function("exports", "module", "require", js)(box.exports, box, () => ({
readState: () => null,
}));
const c = box.exports;
const [a, b, d] = [{ tag: "a" }, { tag: "b" }, { tag: "d" }];
c.keepSavedDetail("P", 1, a);
c.keepSavedDetail("P", 2, b);
c.keepHeldDetail("P", 1, b);
c.setDraftSectionDetail("P", d);
const tag = (x) => x?.tag ?? null;
const out = {
saved1: tag(c.savedSectionDetail("P", 1)),
held1: tag(c.heldSectionDetail("P", 1)),
draft: tag(c.draftDetailOf("P")),
};
c.forgetDetailSlots("P", 1);
out.after1 = [
tag(c.savedSectionDetail("P", 1)),
tag(c.heldSectionDetail("P", 1)),
tag(c.savedSectionDetail("P", 2)),
tag(c.draftDetailOf("P")),
];
c.forgetDetailSlots("P");
out.afterAll = [tag(c.savedSectionDetail("P", 2)), tag(c.draftDetailOf("P"))];
console.log(JSON.stringify(out));
@@ -0,0 +1,60 @@
# -*- coding: utf-8 -*-
"""472-5 5-B — 횡단 상세 객체의 주인 = 상세 가게 하나(칸 셋: 저장 정본 · 초안 전 hold · 노선 초안).
옛 꼴: 계산 묶음 페이지가 상세 객체를 따로 쥠(정본 `canon.detail` · 초안 전 `held.detail` ·
노선 초안 `shown`) — 가게가 판 바뀜(433-16)으로 hold 를 새 판으로 바꿔도 페이지는 옛 객체로
정본을 잡았음.
"""
from __future__ import annotations
import json
import subprocess
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[2]
TS = ROOT / "config" / "node_modules" / "typescript"
def _src(*parts: str) -> str:
return (ROOT.joinpath(*parts)).read_text(encoding="utf-8")
def test_페이지는_상세를_안_쥠() -> None:
page = _src("A00_Common", "design", "design_pipeline_page.ts")
saved = _src("A00_Common", "design", "design_pipeline_page_saved.ts")
assert "let shown" not in page # 노선 초안 상세 = 가게 칸
assert "setDraftSectionDetail(projectId, shown)" in page
assert "detail: () => draftDetailOf(projectId)" in page
assert "detail: SectionDetailResponse | null;" not in page # hold 에 상세 사본 없음
assert "heldSectionDetail(projectId, routeId as number)" in page
assert "let canon: { routeId: unknown; samples: unknown } | null" in saved # 지문만
assert "savedSectionDetail(projectId, canon.routeId)" in saved
def test_가게가_칸을_채움() -> None:
store = _src("B06_Section", "B06_Section_Section_Store.ts")
# 받을 때(세션 · 새로 받음 · 판 바뀜) · 되돌릴 때 = 저장 정본 칸 · 갈아끼움은 안 씀
assert store.count("keepSavedDetail(projectId, routeId,") == 4
swap = store[store.index("export async function swapSectionStations(") :]
assert "keepSavedDetail" not in swap[: swap.index("\n}\n")]
# 판이 바뀌면 hold 도 새 판(433-16)
renew = "if (heldSectionDetail(projectId, routeId) === stored) keepHeldDetail("
assert renew + "projectId, routeId, fresh);" in store
forget = store[store.index("export function forgetSectionMemory(") :]
assert "forgetDetailSlots(projectId);" in forget[: forget.index("\n}\n")]
@pytest.mark.skipif(not TS.is_dir(), reason="프론트엔드 의존성 미설치")
def test_칸_셋_넣고_비움() -> None:
helper = ROOT / "resources" / "tester" / "helper_472_5b_slots.cjs"
run = subprocess.run(
["node", str(helper)], capture_output=True, text=True, encoding="utf-8", timeout=60
)
assert run.returncode == 0, run.stderr
got = json.loads(run.stdout)
assert got["saved1"] == "a" and got["held1"] == "b" and got["draft"] == "d"
assert got["after1"] == [None, None, "b", "d"] # 노선 하나만 · 초안 칸은 그대로
assert got["afterAll"] == [None, None]