feat(공용): 프로젝트 떠남 = 물음 뒤 그 프로젝트 비움 (PLAN 572-4 · 요구 400~403)
- main.ts renderAll 첫머리 — 프로젝트 화면 → 밖이면 leaveProject(지금 프로젝트) · hashchange 가드(셋 물음)를 지난 뒤 - leaveProject 지움 = b_page_state clearSessionKeys(지움 알림) — B05 설계 가게의 미룬 쓰기(1 s)가 떠난 뒤 design-footprint 를 되살리던 흠 막음(ORCA 에서 찾음) - test_401 — 떠남 연결 소스 대조 · 지움 알림 수 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -438,6 +438,20 @@ export function onStateRemoved(fn: (key: string) => void): () => void {
|
||||
return () => removedListeners.delete(fn);
|
||||
}
|
||||
|
||||
/** 세션 칸을 골라 지움(401 떠남) — 칸마다 지움 알림(설계 가게가 미룬 쓰기를 거둠). */
|
||||
export function clearSessionKeys(match: (key: string) => boolean): void {
|
||||
const doomed: string[] = [];
|
||||
try {
|
||||
for (let index = 0; index < window.sessionStorage.length; index += 1) {
|
||||
const key = window.sessionStorage.key(index);
|
||||
if (key !== null && match(key)) doomed.push(key);
|
||||
}
|
||||
} catch {
|
||||
return; // 저장소가 막힌 브라우저 — 지울 것이 없다.
|
||||
}
|
||||
doomed.forEach((key) => writeRaw(key, null));
|
||||
}
|
||||
|
||||
function writeRaw(key: string, value: string | null): void {
|
||||
if (value === null) removedListeners.forEach((fn) => fn(key));
|
||||
try {
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
* ========================================================================== */
|
||||
|
||||
import { CURRENT_PROJECT_ID_KEY } from "../config/config_frontend";
|
||||
import { STATE_REGISTRY, type StateEntry } from "./b_page_state";
|
||||
import { clearSessionKeys, STATE_REGISTRY, type StateEntry } from "./b_page_state";
|
||||
|
||||
/** 이 탭이 마지막으로 들어온 프로젝트. */
|
||||
const ENTERED_KEY = "aislo:entered-project";
|
||||
@@ -212,14 +212,10 @@ export async function wipeIfOwnerChanged(user: string | number): Promise<boolean
|
||||
*/
|
||||
export async function leaveProject(projectId: string): Promise<void> {
|
||||
if (!projectId) return;
|
||||
try {
|
||||
// 프로젝트 칸 열쇠는 모두 프로젝트 번호(uuid)를 품음 — 등록표 · 옛 키 · 페이지 칸을 한 줄로.
|
||||
Object.keys(sessionEntries())
|
||||
.filter((key) => key.includes(projectId))
|
||||
.forEach((key) => window.sessionStorage.removeItem(key));
|
||||
} catch {
|
||||
/* 저장소가 막힌 브라우저 — 지울 것이 없다. */
|
||||
}
|
||||
// 프로젝트 칸 열쇠는 모두 프로젝트 번호(uuid)를 품음 — 등록표 · 옛 키 · 페이지 칸을 한 줄로.
|
||||
// 등록표 창구로 지움 — 지움 알림을 받은 설계 가게가 미뤄 둔 쓰기(1 s)를 거둠(떠난 뒤 되살아남 막음).
|
||||
// ponytail: 아직 한 번도 안 쓴 칸의 미룬 쓰기는 못 거둠 — 생기면 가게에 프로젝트 단위 거둠을 더함.
|
||||
clearSessionKeys((key) => key.includes(projectId));
|
||||
const { purgeProject } = await import("./b_asset_cache");
|
||||
await purgeProject(projectId);
|
||||
}
|
||||
|
||||
@@ -28,9 +28,11 @@ import {
|
||||
ensureInitialHash,
|
||||
setLeaveGuard,
|
||||
draftRoute,
|
||||
isProjectRoute,
|
||||
showWithinPage,
|
||||
takeLeaveGuard,
|
||||
} from "./router";
|
||||
import { currentProjectId, leaveProject } from "./b_project_switch";
|
||||
|
||||
const FOOTERLESS_ROUTES: readonly RoutePath[] = [
|
||||
ROUTES.B01_ACCOUNT,
|
||||
@@ -76,6 +78,10 @@ function bootstrap(): void {
|
||||
"is-bm-route",
|
||||
NATIVE_MENU_ROUTE.test(currentRoute()),
|
||||
); // 전역 글 선택 막음(81-1)
|
||||
// 프로젝트를 떠남(401) — 떠남 물음을 지난 뒤 · 그 프로젝트 세션 칸 · 3D 보관함 몫 비움
|
||||
if (isProjectRoute(shown) && !isProjectRoute(currentRoute())) {
|
||||
void leaveProject(currentProjectId());
|
||||
}
|
||||
const within = showWithinPage(currentRoute()); // B05 ↔ B06 — 보기만 바꿈(24-3a)
|
||||
shown = currentRoute();
|
||||
shownHash = window.location.hash;
|
||||
|
||||
@@ -74,7 +74,7 @@ function openTab() {
|
||||
currentRoute: () => router.route,
|
||||
setLeaveGuard: (fn) => ((router.guard = fn), (router.sets += 1)),
|
||||
});
|
||||
return { win, sw };
|
||||
return { win, sw, ps: load("A00_Common/b_page_state.ts") };
|
||||
}
|
||||
|
||||
const X = "11111111-aaaa-4aaa-8aaa-000000000001";
|
||||
@@ -147,8 +147,12 @@ const keys = (win) => Object.keys(win.sessionStorage.dump()).sort();
|
||||
]) {
|
||||
shared.idb.data.set(`${pid}|${url}`, { key: `${pid}|${url}`, projectId: pid, url });
|
||||
}
|
||||
const removed = [];
|
||||
const stop = A.ps.onStateRemoved((key) => removed.push(key)); // 설계 가게가 미룬 쓰기를 거두는 알림
|
||||
await A.sw.leaveProject(X);
|
||||
stop();
|
||||
out.leave = {
|
||||
notified: removed.length,
|
||||
keys: keys(A.win),
|
||||
expected: [...keep].sort(),
|
||||
entered: A.win.sessionStorage.getItem("aislo:entered-project"),
|
||||
|
||||
@@ -52,6 +52,7 @@ def test_떠남은_그_프로젝트_몫만_지움(out):
|
||||
assert leave["local"] == ["1", "1"] # 취향 · CAD 복구본
|
||||
assert leave["idb"] == [f"{Y}|u1"] # 보관함 = X 줄만 지움
|
||||
assert leave["otherTabKept"] == "1" # 다른 탭 세션은 안 건드림
|
||||
assert leave["notified"] == 13 # 지운 칸마다 지움 알림 — 설계 가게가 미룬 쓰기(1 s)를 거둠
|
||||
|
||||
|
||||
def test_떠남_물음_한_번(out):
|
||||
@@ -96,3 +97,12 @@ def test_떠남_취소_뒤_단계_단추_다시_맞춤():
|
||||
assert "window.dispatchEvent(new Event(ROUTE_RESTORED_EVENT))" in main
|
||||
help_ts = (ROOT / "ui_template/ui_template_help.ts").read_text(encoding="utf-8")
|
||||
assert "window.addEventListener(ROUTE_RESTORED_EVENT, onHash);" in help_ts
|
||||
|
||||
|
||||
def test_떠남은_물음_뒤_한_자리에서_비움():
|
||||
# 572-4 — hashchange 가드(물음)를 지난 renderAll 첫머리 · 프로젝트 화면 → 밖 일 때만
|
||||
main = (ROOT / "A00_Common/main.ts").read_text(encoding="utf-8")
|
||||
render = main[main.index("const renderAll") : main.index("const within")]
|
||||
assert "isProjectRoute(shown) && !isProjectRoute(currentRoute())" in render
|
||||
assert "leaveProject(currentProjectId())" in render
|
||||
assert main.index("takeLeaveGuard()") > main.index("const renderAll") # 물음은 renderAll 앞에서
|
||||
|
||||
Reference in New Issue
Block a user