- 발행일
링크를 따라간 뒤 돌아오는 길이 어긋났다 — 이력에 쌓을 것과 덮어쓸 것을 가르는 기준
링크를 따라간 뒤 돌아오는 길이 어긋났다
표준데이터 검색에서 데이터 프레임만 걸어 목록을 보다가 하나를 눌러 상세로 갑니다. 상세 안에는 참조하는 다른 표준데이터로 가는 링크가 있어요. 그걸 또 누릅니다. 이제 목록 버튼을 누르면 맨 목록입니다. 걸어 뒀던 조건이 통째로 사라져요.
한 홉은 괜찮았습니다. 목록이 상세로 보낼 때 ?back_url=에 자기 주소를 실어 주고, 상세의 목록 버튼이 그걸 읽거든요. 문제는 두 번째 홉부터예요. 상세 안의 링크는 back_url을 안 실었습니다.
이날 고친 건 이것 하나가 아니었습니다. 돌아오는 길이 어긋나는 자리가 화면마다 하나씩 있었어요.
복귀 주소는 받은 것을 그대로 넘긴다
첫 번째 판단은 상세에서 상세로 갈 때 무엇을 back_url에 실을 것인가였습니다.
| 방법 | 결과 | 판단 |
|---|---|---|
| ① 안 싣는다(현행) | 두 번째 홉부터 맨 목록 | 문제 그 자체 |
| ② 자기 주소를 싣는다 | 다음 화면의 목록 버튼이 목록이 아니라 상세로 감. 한 홉마다 주소가 중첩 인코딩되며 길어짐 | 기각 |
③ 받은 back_url을 그대로 넘긴다 | 몇 홉을 타든 출발한 목록이 복귀처. 라벨도 맞음. 대신 중간에 거쳐 온 상세는 못 짚음 | 채택 |
# 화면 안의 교차 링크(참조 자원·구성 필드·표준·OID)가 함께 실어 보낼 조각.
# **받은 복귀 주소를 그대로 넘긴다** — 자기 경로를 실으면 한 홉마다 주소가 중첩
# 인코딩되며 길어지고, 다음 화면의 `목록` 버튼이 목록이 아닌 상세로 간다.
# 통과시키면 몇 홉을 타든 출발한 목록이 복귀처로 남는다(instance_tree.py
# `_card_detail_query` 와 같은 규약). 대신 중간에 거쳐 온 상세는 못 짚는다 —
# 그 길은 브라우저 뒤로가기가 맡는다.
context['cross_link_query'] = back_url_query(safe_back_url(self.request))
뷰가 cross_link_query 하나를 만들고, 템플릿의 교차 링크마다 그걸 붙입니다. 참조 자원, 구성 필드, 표준, OID. 붙인 링크가 열두 곳이었어요.
새 탭으로 여는 링크와 바깥으로 나가는 링크에는 안 붙였습니다. 돌아올 자리가 없으니까요. 트리 화면 계열은 목록 버튼 자체가 없어서 사슬이 거기서 끝납니다.
③의 대가는 분명합니다. 중간에 거쳐 온 상세로는 목록 버튼으로 못 돌아가요. 그 길은 브라우저 뒤로가기가 맡습니다. 그러니 뒤로가기가 제대로 동작해야 이 선택이 성립합니다. 그게 이날의 나머지 절반이었어요.
뒤로가기도 복귀 경로다
?back_url=은 목록 버튼만 고칩니다. 사람은 브라우저 뒤로가기도 같이 씁니다. 이력 자체가 어긋나 있으면 버튼을 아무리 손봐도 화면이 애매해져요.
이력이 어긋나는 방식은 두 가지였습니다. 쌓이지 말아야 할 게 쌓이고, 남아야 할 게 안 남는 것.
쌓이지 말아야 할 것부터. 검색 목록의 유형 칩과 범위 필터가 location.href로 이동하고 있었습니다. 조건을 만질 때마다 이력이 하나씩 쌓여요. 다섯 번 좁히고 화면을 벗어나려면 뒤로가기를 다섯 번 눌러야 합니다.
// 칩은 새 목적지가 아니라 같은 목록의 조건 변경이다 — push 하면 좁힐 때마다
// 이력이 쌓여 검색 화면을 벗어나려면 뒤로가기를 그 횟수만큼 눌러야 한다.
location.replace(location.pathname + (qs ? "?" + qs : ""));
칩 하나만 고치면 같은 화면 안에서 규칙이 갈립니다. 범위 필터의 적용·해제·2차 선택·전체 초기화까지 한 화면의 조건 장치 전부를 replace로 맞췄어요.
페이지네이션은 push로 뒀습니다. 쪽 넘김은 조건 변경보다 이동에 가까워서, 뒤로가기가 앞 쪽으로 가는 편이 자연스럽다고 봤어요. 공용 매크로라 바꾸면 전 화면에 닿기도 하고요. 이 판단은 문서에 "아직 push로 둔 것"으로 적어 뒀습니다. 확신보다는 보류에 가까워요.
트리 화면은 자기 선택을 주소에 남긴다
남아야 할 게 안 남는 쪽. 관리 인스턴스 화면은 왼쪽 트리에서 노드를 고르면 오른쪽에 상세가 뜨는 구조인데, 주소가 안 바뀝니다. 화면 안의 링크로 나갔다가 뒤로가기로 돌아오면 트리가 처음 상태로 풀려요. 보던 자리를 다시 찾아야 합니다.
용어 트리와 OID 트리는 이미 ?selected_node=를 replaceState로 남기고 있었습니다. 같은 규약을 인스턴스 화면과 공개 카탈로그·개념체계 트리에도 붙였어요.
// 위의 짝 — 주소에 실린 노드를 되살린다. 이 트리는 네임스페이스를 열어야 자식이 오는
// 지연 로딩이지만 조상 경로를 주소에 실을 필요는 없다. classItems·instItems 가 화면에
// 통째로 실려 있어 거기서 되짚으면 된다.
function restoreSelectedNodeFromQuery() {
지연 로딩 트리라 조상 경로까지 주소에 실어야 하나 고민했는데, 이 화면은 클래스·인스턴스 목록이 통째로 실려 있어서 노드 id 하나에서 네임스페이스를 되짚을 수 있었습니다.
공개 트리 쪽은 되살리는 코드가 원래 있었습니다. getSelectedNodeIdFromUrl·restoredChip·restoredPage. 쓰는 쪽만 없었던 거예요. 여기서 함정이 하나 있었는데, 같은 파일에 backUrl()이라는 비슷한 함수가 있습니다.
// backUrl() 을 그대로 쓰지 않는 이유 — 그쪽은 `pathname` 에 세 키만 새로 조립해 나머지
// 파라미터를 버린다(나가는 주소라 selected_node 가 겹쳐 쌓이는 걸 막는 게 목적이다).
// 여기는 **지금 주소**를 고쳐 쓰는 자리라, 화면이 들고 있던 다른 파라미터까지 날리면 안 된다.
나가는 주소를 만드는 함수와 지금 주소를 고쳐 쓰는 함수는 같아 보여도 다릅니다. 전자는 세 키만 새로 조립해서 중첩을 막고, 후자는 기존 파라미터를 보존해야 해요.
건너뛰기는 쌓고, 손잡이도 같이 세운다
인스턴스 화면에는 한 가지가 더 있었습니다. 상세 표 안의 참조 칩을 누르면 같은 화면 안에서 다른 데이터로 옮겨 갑니다. 트리를 눌러 옮겨 다니는 것과 코드상으로는 같은 일이에요. 그런데 사용자에게는 다릅니다. 링크를 따라간 겁니다.
// 건너뛰기는 **쌓는다**(push) — 사용자에겐 링크를 따라간 것이라 뒤로가기가 되짚어야
// 한다. 트리 선택은 같은 화면의 상태일 뿐이라 덮어쓴다(replace).
if (isJump) {
history.pushState(null, "", url);
jumpDepth += 1;
} else {
history.replaceState(null, "", url);
}
기준은 앞과 같습니다. 다른 곳으로 갔나, 같은 화면의 상태가 바뀌었나. 트리 클릭은 훑어보기라 replace, 참조 칩은 이동이라 push.
replaceState만 걸면 최악이 됩니다. 주소는 움직이는데 뒤로가기는 화면을 통째로 벗어나요. 이동한 것처럼 보이는데 되돌아갈 수 없는 상태. 주소를 움직일 거면 되돌릴 길을 함께 놓아야 합니다.
그리고 화살표만으로는 부족했습니다. 눌러 보기 전엔 뭐가 나올지 모르니까요. ‹ 이전 데이터로 버튼을 화면에 세웠는데, 이 버튼은 별도 스택을 들지 않습니다.
// `이전 데이터로` — 되돌리는 일은 브라우저 이력이 맡고 버튼은 그 손잡이다. 따로 스택을
// 들면 이력과 어긋나 버튼과 화살표가 다른 곳으로 간다.
// 건너온 적이 없으면 감춘다 — 누르면 화면 밖으로 나가 버리는 버튼은 두면 안 된다.
history.back() 한 칸이 전부예요. 따로 스택을 들면 버튼과 브라우저 화살표가 다른 곳으로 갑니다. 건너온 횟수 jumpDepth는 버튼을 보일지 말지에만 쓰고, 어디로 갈지는 브라우저가 정해요. 건너온 적이 없으면 감춥니다. 누르면 화면 밖으로 나가 버리는 버튼은 두면 안 되니까요.
스펙이 잠그던 성질이 바뀌었다
카탈로그 패널의 칩 스펙에 칩을 누르면 주소를 안 바꾸고 카드만 좁혀진다라는 단언이 있었습니다. 이제 칩 전환이 replaceState로 ?type=을 남기니까 그 단언은 깨져요.
그런데 그 스펙이 지키려던 성질은 "주소 문자열이 같다"가 아니었습니다. "전체 새로고침이 없다" 였어요. 주소 동일성은 그걸 확인하는 수단이었을 뿐인데, 수단이 성질로 굳어 있었던 겁니다.
// 전체 새로고침이면 이 표식이 날아간다 — 주소 문자열은 replaceState 로 바뀌므로
// 동일성으로는 더 이상 판별할 수 없다.
await page.evaluate(() => {
window.__panelNoReload = 1;
});
문서에 표식을 심고 클릭 뒤에도 살아 있는지 봅니다. 단언을 성질 쪽으로 옮겼어요.
이 파일은 사실 통째로 서 있었습니다. 원인은 데이터가 아니라 선택자였어요. 분류 칩이 8월 24일에 유형 칩으로 바뀌면서 data-category-chip을 기다리다 죽고 있었습니다. 되살리면서 같이 고쳤는데, 되살리고 보니 3건은 여전히 데이터가 없어 스킵입니다. 카탈로그 노드 8개 전부 담긴 표준데이터가 0건이라 카드를 전제하는 시나리오가 설 자리가 없어요.
검증
- 데이터 프레임 조건을 건 목록 → 상세 → 상세 안 교차 링크로 두 번째 상세 →
목록버튼이 조건까지 그대로 복귀하는 것을 e2e로 잠금. 고치기 전에는 두 번째 홉에서 맨 목록으로 떨어졌습니다. - 관리 인스턴스 화면에서 클래스를 고른 뒤 다른 화면으로 갔다가 뒤로가기 → 트리 선택과 상세가 함께 되살아나는 것 확인.
selected_node가 실린 주소로 바로 들어와도 같은 자리가 열리는 것 확인. - 참조 칩으로 건너뛴 뒤 브라우저 뒤로가기와
이전 데이터로버튼이 같은 자리로 돌아오는 것, 건너온 적이 없으면 버튼이 안 서는 것, 트리 노드로 옮겨 다닌 건 건너뜀으로 안 세는 것 세 가지를 스펙으로 잠금. - 공개 카탈로그·개념체계 트리에서 노드·칩·쪽을 고른 뒤 뒤로가기로 돌아와도 그대로인 것 확인.
- 유형 칩과 범위 필터를 여러 번 만진 뒤 뒤로가기 한 번에 검색 화면을 벗어나는 것 확인.
- 회귀 스펙 네 개를 새로 만들었습니다.
instance-detail-backurl-relay·tree-selected-node-url·instance-selected-node-url·instance-jump-back.
남은 것 · 한계
- 중간에 거쳐 온 상세로는
목록버튼으로 못 갑니다. 받은back_url을 통과시키기로 한 대가예요. 뒤로가기가 맡는다고 했지만, 세 홉을 탄 사람이 두 번째 상세로 돌아가려면 그게 몇 칸인지 세어야 합니다. - 페이지네이션만
push로 남겨 뒀습니다. 조건 변경과 이동의 경계에 있는데, 공용 매크로라 확인 없이 못 바꿔서 보류한 거예요. 같은 화면에서 칩은 덮어쓰고 쪽은 쌓이는 상태입니다. - 필터는 여전히 새로고침입니다.
href를replace로 바꿔서 이력만 잡았고, 화면이 하얘지는 건 그대로예요. 이건 사흘 뒤에 조각 교체로 다시 손봤습니다. - 카탈로그 칩 스펙 3건은 데이터가 없어 스킵입니다. 칩 전환·상세 복귀·늦은 응답 가드가 그 화면에서는 아직 미검증이에요.
- 인스턴스 화면 스펙은 등록된 인스턴스 전부를 JSON으로 싣는 화면이라 한 번 여는 데 20초대입니다. 타임아웃을 180초로 늘려 뒀는데, 화면이 빨라지면 되돌려야 하는 값이 스펙 파일에 남아 있습니다.
관련 글: 데이터 맵에서 세 단계 들어간 자리를 주소에 남겼다 · 편집 중 트리를 누르면 입력이 사라졌다 · 검색 조건을 바꿀 때 새로고침 대신 조각 9개를 갈아끼웠다