fix: show the reason the server gave for a failed action
Every failure in the management screens printed a guess. Deleting a working copy said "it may be published, or something may reference it, or someone may have edited it first" — three maybes, while the server had answered with exactly one: "이 기록을 참조하는 곳이 있어 삭제할 수 없습니다". A version conflict read as "in use" because the same sentence covered both, and an author watching some deletions succeed and others fail had no way to tell them apart. The gateway now carries the server's client-safe message and the screens show it. The canned sentences remain only as a fallback for a failure that never reached the server. The topic status label said "사용 중" for every active topic, including one created seconds earlier that nothing references. Next to a refusal about records that use a topic, the two read as the same statement. It says "활성" now, which is what the status means. Publishing also stops demanding a finished document — the mock validator moves with the real one, so what an author sees against fixtures matches production.
This commit is contained in:
@@ -0,0 +1 @@
|
|||||||
|
[ 1019ms] [ERROR] Failed to load resource: the server responded with a status of 401 (Unauthorized) @ https://hyeonworks.com/api/v1/studio/session:0
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
[ 39361ms] [ERROR] Failed to load resource: the server responded with a status of 409 (Conflict) @ https://hyeonworks.com/api/v1/studio/cases/b09f168b-b205-478f-b60f-83872d1b4d01:0
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
[ 4651ms] [ERROR] Failed to load resource: the server responded with a status of 409 (Conflict) @ https://hyeonworks.com/api/v1/studio/cases/b09f168b-b205-478f-b60f-83872d1b4d01:0
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
- generic [ref=f1e3]:
|
||||||
|
- banner [ref=f1e4]:
|
||||||
|
- generic [ref=f1e5]: prod
|
||||||
|
- main [ref=f1e6]:
|
||||||
|
- heading "Sign in to your account" [level=1] [ref=f1e8]
|
||||||
|
- generic [ref=f1e12]:
|
||||||
|
- generic [ref=f1e13]:
|
||||||
|
- generic [ref=f1e14]: Username or email
|
||||||
|
- textbox "Username or email" [active] [ref=f1e17]
|
||||||
|
- generic [ref=f1e18]:
|
||||||
|
- generic [ref=f1e19]: Password
|
||||||
|
- generic [ref=f1e21]:
|
||||||
|
- textbox "Password" [ref=f1e24]
|
||||||
|
- button "Show password" [ref=f1e26] [cursor=pointer]:
|
||||||
|
- generic [ref=f1e27]:
|
||||||
|
- button "Sign In" [ref=f1e30] [cursor=pointer]
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
- generic [ref=f2e3]:
|
||||||
|
- link "본문으로 건너뛰기" [ref=f2e4] [cursor=pointer]:
|
||||||
|
- /url: "#main-content"
|
||||||
|
- banner [ref=f2e5]:
|
||||||
|
- generic [ref=f2e6]:
|
||||||
|
- link "TechLog 홈" [ref=f2e8] [cursor=pointer]:
|
||||||
|
- /url: /
|
||||||
|
- text: TechLog
|
||||||
|
- generic [ref=f2e9]:
|
||||||
|
- button "TechLog 검색 열기" [ref=f2e11] [cursor=pointer]: 검색
|
||||||
|
- group [ref=f2e12]:
|
||||||
|
- generic "메뉴" [ref=f2e13] [cursor=pointer]
|
||||||
|
- main [ref=f2e14]:
|
||||||
|
- region [ref=f2e15]:
|
||||||
|
- heading "TechLog" [level=1] [ref=f2e16]
|
||||||
|
- paragraph [ref=f2e17]: 문제를 재현하고 검증해 운영 가능한 설계로 연결합니다.
|
||||||
|
- region [ref=f2e18]:
|
||||||
|
- generic [ref=f2e19]:
|
||||||
|
- generic [ref=f2e20]:
|
||||||
|
- paragraph [ref=f2e21]: Index
|
||||||
|
- heading "최근 기록" [level=2] [ref=f2e22]
|
||||||
|
- link "모든 기록 탐색" [ref=f2e23] [cursor=pointer]:
|
||||||
|
- /url: /explore
|
||||||
|
- list [ref=f2e24]:
|
||||||
|
- listitem [ref=f2e25]:
|
||||||
|
- link "RELEASE 2026.08.21 첫 공개 공개 사이트와 Studio 작성 흐름을 처음으로 실제 서버에 올렸습니다. TechLog · TechLog" [ref=f2e26] [cursor=pointer]:
|
||||||
|
- /url: /releases/0.1.0
|
||||||
|
- generic [ref=f2e27]:
|
||||||
|
- generic [ref=f2e28]: RELEASE
|
||||||
|
- time [ref=f2e29]: 2026.08.21
|
||||||
|
- generic [ref=f2e30]:
|
||||||
|
- heading "첫 공개" [level=3] [ref=f2e31]
|
||||||
|
- paragraph [ref=f2e32]: 공개 사이트와 Studio 작성 흐름을 처음으로 실제 서버에 올렸습니다.
|
||||||
|
- paragraph [ref=f2e33]: TechLog · TechLog
|
||||||
|
- generic [ref=f2e34]: ↗
|
||||||
|
- region [ref=f2e35]:
|
||||||
|
- generic [ref=f2e37]:
|
||||||
|
- paragraph [ref=f2e38]: Explore
|
||||||
|
- heading "어떤 맥락으로 읽을까요?" [level=2] [ref=f2e39]
|
||||||
|
- list [ref=f2e40]:
|
||||||
|
- listitem [ref=f2e41]:
|
||||||
|
- link "문제를 따라가며 검증 과정을 읽습니다 Case" [ref=f2e42] [cursor=pointer]:
|
||||||
|
- /url: /explore/cases
|
||||||
|
- generic [ref=f2e43]: 문제를 따라가며 검증 과정을 읽습니다
|
||||||
|
- strong [ref=f2e44]: Case
|
||||||
|
- generic [ref=f2e45]: ↗
|
||||||
|
- listitem [ref=f2e46]:
|
||||||
|
- link "다시 찾을 수 있는 기술 기준을 확인합니다 Reference" [ref=f2e47] [cursor=pointer]:
|
||||||
|
- /url: /explore/references
|
||||||
|
- generic [ref=f2e48]: 다시 찾을 수 있는 기술 기준을 확인합니다
|
||||||
|
- strong [ref=f2e49]: Reference
|
||||||
|
- generic [ref=f2e50]: ↗
|
||||||
|
- listitem [ref=f2e51]:
|
||||||
|
- link "아직 끝나지 않은 판단과 다음 검증을 봅니다 OpenQuestion" [ref=f2e52] [cursor=pointer]:
|
||||||
|
- /url: /explore/questions
|
||||||
|
- generic [ref=f2e53]: 아직 끝나지 않은 판단과 다음 검증을 봅니다
|
||||||
|
- strong [ref=f2e54]: OpenQuestion
|
||||||
|
- generic [ref=f2e55]: ↗
|
||||||
|
- listitem [ref=f2e56]:
|
||||||
|
- link "여러 기록을 하나의 시스템 맥락에서 연결합니다 Project" [ref=f2e57] [cursor=pointer]:
|
||||||
|
- /url: /projects
|
||||||
|
- generic [ref=f2e58]: 여러 기록을 하나의 시스템 맥락에서 연결합니다
|
||||||
|
- strong [ref=f2e59]: Project
|
||||||
|
- generic [ref=f2e60]: ↗
|
||||||
|
- contentinfo [ref=f2e61]:
|
||||||
|
- generic [ref=f2e62]:
|
||||||
|
- generic [ref=f2e63]:
|
||||||
|
- paragraph [ref=f2e64]: 동현
|
||||||
|
- paragraph [ref=f2e65]: 문제를 재현하고 검증해 운영 가능한 설계로 연결합니다.
|
||||||
|
- generic [ref=f2e66]:
|
||||||
|
- link "프로필" [ref=f2e67] [cursor=pointer]:
|
||||||
|
- /url: /profile
|
||||||
|
- link "변경 기록" [ref=f2e68] [cursor=pointer]:
|
||||||
|
- /url: /releases
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
- generic [ref=f3e22]:
|
||||||
|
- link "본문으로 건너뛰기" [ref=f3e23] [cursor=pointer]:
|
||||||
|
- /url: "#main-content"
|
||||||
|
- banner [ref=f3e24]:
|
||||||
|
- generic [ref=f3e25]:
|
||||||
|
- link "TechLog Studio" [ref=f3e26] [cursor=pointer]:
|
||||||
|
- /url: /studio
|
||||||
|
- text: TechLog
|
||||||
|
- generic [ref=f3e27]: Studio
|
||||||
|
- navigation "Studio 주 탐색" [ref=f3e29]:
|
||||||
|
- link "작업본" [ref=f3e30] [cursor=pointer]:
|
||||||
|
- /url: /studio/documents
|
||||||
|
- link "게시 기록" [ref=f3e31] [cursor=pointer]:
|
||||||
|
- /url: /studio/publications
|
||||||
|
- link "새 문서" [ref=f3e32] [cursor=pointer]:
|
||||||
|
- /url: /studio/documents/new
|
||||||
|
- link "주제·프로젝트" [ref=f3e33] [cursor=pointer]:
|
||||||
|
- /url: /studio/taxonomy
|
||||||
|
- link "릴리즈" [ref=f3e34] [cursor=pointer]:
|
||||||
|
- /url: /studio/releases
|
||||||
|
- link "공개 사이트 보기" [ref=f3e35] [cursor=pointer]:
|
||||||
|
- /url: /
|
||||||
|
- button "로그아웃" [ref=f3e36]
|
||||||
|
- main [ref=f3e37]:
|
||||||
|
- generic [ref=f3e38]:
|
||||||
|
- generic [ref=f3e39]:
|
||||||
|
- generic [ref=f3e40]:
|
||||||
|
- paragraph [ref=f3e41]: WORKING COPIES
|
||||||
|
- heading "작업본" [level=1] [ref=f3e42]
|
||||||
|
- paragraph [ref=f3e43]: 세션에 있는 Case, Reference, Question, Decision을 찾고 다음 작업으로 이동합니다.
|
||||||
|
- link "새 문서" [ref=f3e44] [cursor=pointer]:
|
||||||
|
- /url: /studio/documents/new
|
||||||
|
- region "작업본 검색과 필터" [ref=f3e45]:
|
||||||
|
- search [ref=f3e46]:
|
||||||
|
- generic [ref=f3e47]: 검색
|
||||||
|
- generic [ref=f3e48]:
|
||||||
|
- searchbox "검색" [ref=f3e49]
|
||||||
|
- button "검색" [ref=f3e50]
|
||||||
|
- generic [ref=f3e51]:
|
||||||
|
- text: 종류
|
||||||
|
- combobox "종류" [ref=f3e52]:
|
||||||
|
- option "전체" [selected]
|
||||||
|
- option "Case"
|
||||||
|
- option "Reference"
|
||||||
|
- option "Question"
|
||||||
|
- option "Decision"
|
||||||
|
- generic [ref=f3e53]:
|
||||||
|
- text: 상태
|
||||||
|
- combobox "상태" [ref=f3e54]:
|
||||||
|
- option "전체" [selected]
|
||||||
|
- option "게시 전"
|
||||||
|
- option "게시 중"
|
||||||
|
- option "게시 취소"
|
||||||
|
- paragraph [ref=f3e55]:
|
||||||
|
- generic [ref=f3e56]: 2개 표시 중
|
||||||
|
- alert [ref=f3e57]: 삭제하지 못했습니다. 게시 중이거나, 이 기록을 참조하는 곳이 있거나, 다른 곳에서 먼저 수정되었을 수 있습니다.
|
||||||
|
- generic [ref=f3e58]:
|
||||||
|
- article [ref=f3e1]:
|
||||||
|
- paragraph [ref=f3e2]: Case
|
||||||
|
- generic [ref=f3e3]:
|
||||||
|
- heading [level=2] [ref=f3e4]:
|
||||||
|
- link "게시 흐름 확인" [ref=f3e5] [cursor=pointer]:
|
||||||
|
- /url: /studio/documents/b09f168b-b205-478f-b60f-83872d1b4d01/edit
|
||||||
|
- paragraph [ref=f3e6]: 프로젝트 미지정
|
||||||
|
- generic [ref=f3e7]:
|
||||||
|
- generic [ref=f3e8]:
|
||||||
|
- term [ref=f3e9]: 상태
|
||||||
|
- definition [ref=f3e10]: 게시 취소
|
||||||
|
- generic [ref=f3e11]:
|
||||||
|
- term [ref=f3e12]: 다음
|
||||||
|
- definition [ref=f3e13]:
|
||||||
|
- link "검증하기" [ref=f3e14] [cursor=pointer]:
|
||||||
|
- /url: /studio/documents/b09f168b-b205-478f-b60f-83872d1b4d01/validation
|
||||||
|
- generic [ref=f3e15]:
|
||||||
|
- term [ref=f3e16]: 수정
|
||||||
|
- definition [ref=f3e17]:
|
||||||
|
- time [ref=f3e18]: 2026. 8. 21.
|
||||||
|
- button "삭제" [ref=f3e19]
|
||||||
|
- article [ref=f3e59]:
|
||||||
|
- paragraph [ref=f3e60]: Case
|
||||||
|
- generic [ref=f3e61]:
|
||||||
|
- heading [level=2] [ref=f3e62]:
|
||||||
|
- link "브라우저에서 토큰을 어디까지 다뤄야 하는가..?" [ref=f3e63] [cursor=pointer]:
|
||||||
|
- /url: /studio/documents/f363edc8-2c13-4995-acda-934237034a85/edit
|
||||||
|
- paragraph [ref=f3e64]: 프로젝트 미지정
|
||||||
|
- generic [ref=f3e65]:
|
||||||
|
- generic [ref=f3e66]:
|
||||||
|
- term [ref=f3e67]: 상태
|
||||||
|
- definition [ref=f3e68]: 게시 전
|
||||||
|
- generic [ref=f3e69]:
|
||||||
|
- term [ref=f3e70]: 다음
|
||||||
|
- definition [ref=f3e71]:
|
||||||
|
- link "검증하기" [ref=f3e72] [cursor=pointer]:
|
||||||
|
- /url: /studio/documents/f363edc8-2c13-4995-acda-934237034a85/validation
|
||||||
|
- generic [ref=f3e73]:
|
||||||
|
- term [ref=f3e74]: 수정
|
||||||
|
- definition [ref=f3e75]:
|
||||||
|
- time [ref=f3e76]: 2026. 8. 21.
|
||||||
|
- button "삭제" [ref=f3e77]
|
||||||
|
- paragraph [ref=f3e78]
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
- generic [ref=f4e3]:
|
||||||
|
- link "본문으로 건너뛰기" [ref=f4e4] [cursor=pointer]:
|
||||||
|
- /url: "#main-content"
|
||||||
|
- banner [ref=f4e5]:
|
||||||
|
- generic [ref=f4e6]:
|
||||||
|
- link "TechLog Studio" [ref=f4e7] [cursor=pointer]:
|
||||||
|
- /url: /studio
|
||||||
|
- text: TechLog
|
||||||
|
- generic [ref=f4e8]: Studio
|
||||||
|
- navigation "Studio 주 탐색" [ref=f4e10]:
|
||||||
|
- link "작업본" [ref=f4e11] [cursor=pointer]:
|
||||||
|
- /url: /studio/documents
|
||||||
|
- link "게시 기록" [ref=f4e12] [cursor=pointer]:
|
||||||
|
- /url: /studio/publications
|
||||||
|
- link "새 문서" [ref=f4e13] [cursor=pointer]:
|
||||||
|
- /url: /studio/documents/new
|
||||||
|
- link "주제·프로젝트" [ref=f4e14] [cursor=pointer]:
|
||||||
|
- /url: /studio/taxonomy
|
||||||
|
- link "릴리즈" [ref=f4e15] [cursor=pointer]:
|
||||||
|
- /url: /studio/releases
|
||||||
|
- link "공개 사이트 보기" [ref=f4e16] [cursor=pointer]:
|
||||||
|
- /url: /
|
||||||
|
- button "로그아웃" [ref=f4e17]
|
||||||
|
- main [ref=f4e18]:
|
||||||
|
- generic [ref=f4e19]:
|
||||||
|
- generic [ref=f4e20]:
|
||||||
|
- paragraph [ref=f4e21]: NEW WORKING COPY
|
||||||
|
- heading "새 문서" [level=1] [ref=f4e22]
|
||||||
|
- paragraph [ref=f4e23]: 목적에 맞는 기록 종류를 선택하면 빈 작업본을 만들고 바로 편집을 시작합니다.
|
||||||
|
- generic [ref=f4e24]:
|
||||||
|
- group "문서 종류" [ref=f4e25]:
|
||||||
|
- generic [ref=f4e27] [cursor=pointer]:
|
||||||
|
- radio "Case 문제를 재현하고 검증한 결론을 기록합니다. 문제 · 결론 · 환경 · 재현 · 본문" [checked] [active] [ref=f4e28]
|
||||||
|
- strong [ref=f4e29]: Case
|
||||||
|
- generic [ref=f4e30]: 문제를 재현하고 검증한 결론을 기록합니다.
|
||||||
|
- generic [ref=f4e31]: 문제 · 결론 · 환경 · 재현 · 본문
|
||||||
|
- generic [ref=f4e32] [cursor=pointer]:
|
||||||
|
- radio "Reference 반복해서 적용할 기술 기준을 정리합니다. 목적 · 규칙 · 적용 조건 · 예외 · 예시" [ref=f4e33]
|
||||||
|
- strong [ref=f4e34]: Reference
|
||||||
|
- generic [ref=f4e35]: 반복해서 적용할 기술 기준을 정리합니다.
|
||||||
|
- generic [ref=f4e36]: 목적 · 규칙 · 적용 조건 · 예외 · 예시
|
||||||
|
- generic [ref=f4e37] [cursor=pointer]:
|
||||||
|
- radio "Question 아직 닫히지 않은 판단과 다음 검증을 관리합니다. 상태 · 사실 · 가정 · 미지수 · 선택지" [ref=f4e38]
|
||||||
|
- strong [ref=f4e39]: Question
|
||||||
|
- generic [ref=f4e40]: 아직 닫히지 않은 판단과 다음 검증을 관리합니다.
|
||||||
|
- generic [ref=f4e41]: 상태 · 사실 · 가정 · 미지수 · 선택지
|
||||||
|
- generic [ref=f4e42] [cursor=pointer]:
|
||||||
|
- radio "Decision 프로젝트가 선택한 방향과 그 근거·영향을 기록합니다. 상태 · 결정일 · 결정문 · 판단 이유 · 영향 · 근거" [ref=f4e43]
|
||||||
|
- strong [ref=f4e44]: Decision
|
||||||
|
- generic [ref=f4e45]: 프로젝트가 선택한 방향과 그 근거·영향을 기록합니다.
|
||||||
|
- generic [ref=f4e46]: 상태 · 결정일 · 결정문 · 판단 이유 · 영향 · 근거
|
||||||
|
- generic [ref=f4e47]:
|
||||||
|
- button "작업본 만들기" [ref=f4e48]
|
||||||
|
- paragraph [ref=f4e49]: 이 화면의 작업본은 현재 Studio 세션에서만 유지됩니다.
|
||||||
|
- paragraph [ref=f4e50]
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
- generic [ref=f4e3]:
|
||||||
|
- link "본문으로 건너뛰기" [ref=f4e4] [cursor=pointer]:
|
||||||
|
- /url: "#main-content"
|
||||||
|
- banner [ref=f4e5]:
|
||||||
|
- generic [ref=f4e6]:
|
||||||
|
- link "TechLog Studio" [ref=f4e7] [cursor=pointer]:
|
||||||
|
- /url: /studio
|
||||||
|
- text: TechLog
|
||||||
|
- generic [ref=f4e8]: Studio
|
||||||
|
- navigation "Studio 주 탐색" [ref=f4e10]:
|
||||||
|
- link "작업본" [ref=f4e11] [cursor=pointer]:
|
||||||
|
- /url: /studio/documents
|
||||||
|
- link "게시 기록" [ref=f4e12] [cursor=pointer]:
|
||||||
|
- /url: /studio/publications
|
||||||
|
- link "새 문서" [ref=f4e13] [cursor=pointer]:
|
||||||
|
- /url: /studio/documents/new
|
||||||
|
- link "주제·프로젝트" [ref=f4e14] [cursor=pointer]:
|
||||||
|
- /url: /studio/taxonomy
|
||||||
|
- link "릴리즈" [ref=f4e15] [cursor=pointer]:
|
||||||
|
- /url: /studio/releases
|
||||||
|
- link "공개 사이트 보기" [ref=f4e16] [cursor=pointer]:
|
||||||
|
- /url: /
|
||||||
|
- button "로그아웃" [ref=f4e17]
|
||||||
|
- main [ref=f4e18]:
|
||||||
|
- generic [ref=f4e51]:
|
||||||
|
- tablist "문서 편집 화면" [ref=f4e52]:
|
||||||
|
- tab "편집" [selected] [ref=f4e53]
|
||||||
|
- tab "즉시 미리보기" [ref=f4e54]
|
||||||
|
- generic [ref=f4e55]:
|
||||||
|
- tabpanel "편집" [ref=f4e57]:
|
||||||
|
- generic [ref=f4e58]:
|
||||||
|
- paragraph [ref=f4e59]: CASE · VERSION 1
|
||||||
|
- heading "문서 편집" [level=1] [ref=f4e60]
|
||||||
|
- paragraph [ref=f4e61]: 제목 없는 작업본
|
||||||
|
- region [ref=f4e62]:
|
||||||
|
- generic [ref=f4e63]:
|
||||||
|
- paragraph [ref=f4e64]: DOCUMENT
|
||||||
|
- heading "기본 정보" [level=2] [ref=f4e65]
|
||||||
|
- generic [ref=f4e66]:
|
||||||
|
- generic [ref=f4e67]:
|
||||||
|
- generic [ref=f4e68]: 제목
|
||||||
|
- textbox "제목" [ref=f4e69]
|
||||||
|
- generic [ref=f4e70]:
|
||||||
|
- generic [ref=f4e71]: slug
|
||||||
|
- textbox "slug" [ref=f4e72]:
|
||||||
|
- /placeholder: 비우면 제목에서 만듭니다 (영문 소문자·숫자·하이픈)
|
||||||
|
- generic [ref=f4e73]:
|
||||||
|
- generic [ref=f4e74]: 요약
|
||||||
|
- textbox "요약" [ref=f4e75]
|
||||||
|
- generic [ref=f4e76]:
|
||||||
|
- generic [ref=f4e77]: Topic
|
||||||
|
- combobox "Topic" [ref=f4e78]:
|
||||||
|
- option "선택하지 않음" [selected]
|
||||||
|
- option "OAuth/OIDC 인증 경계"
|
||||||
|
- generic [ref=f4e79]:
|
||||||
|
- generic [ref=f4e80]: Project
|
||||||
|
- combobox "Project" [ref=f4e81]:
|
||||||
|
- option "미지정" [selected]
|
||||||
|
- option "Backend Clean Architecture"
|
||||||
|
- option "KeyCloak Patterns"
|
||||||
|
- option "Liner N + 1문제"
|
||||||
|
- group "관계" [ref=f4e82]:
|
||||||
|
- paragraph [ref=f4e84]: 연결한 공개 기록이 없습니다.
|
||||||
|
- button "관계 추가" [ref=f4e85]
|
||||||
|
- region [ref=f4e86]:
|
||||||
|
- generic [ref=f4e87]:
|
||||||
|
- paragraph [ref=f4e88]: CASE
|
||||||
|
- heading "문제와 검증" [level=2] [ref=f4e89]
|
||||||
|
- generic [ref=f4e90]:
|
||||||
|
- generic [ref=f4e91]:
|
||||||
|
- generic [ref=f4e92]: 문제
|
||||||
|
- textbox "문제" [ref=f4e93]
|
||||||
|
- generic [ref=f4e94]:
|
||||||
|
- generic [ref=f4e95]: 결론
|
||||||
|
- textbox "결론" [ref=f4e96]
|
||||||
|
- generic [ref=f4e97]:
|
||||||
|
- generic [ref=f4e98]: 검증 환경
|
||||||
|
- textbox "검증 환경" [ref=f4e99]
|
||||||
|
- generic [ref=f4e100]:
|
||||||
|
- generic [ref=f4e101]: 재현 조건
|
||||||
|
- textbox "재현 조건" [ref=f4e102]
|
||||||
|
- generic [ref=f4e103]:
|
||||||
|
- generic [ref=f4e104]: 마지막 검증일
|
||||||
|
- textbox "마지막 검증일" [ref=f4e105]
|
||||||
|
- generic [ref=f4e106]:
|
||||||
|
- generic [ref=f4e107]: 본문 Markdown
|
||||||
|
- textbox "본문 Markdown" [ref=f4e108]
|
||||||
|
- generic [ref=f4e109]:
|
||||||
|
- paragraph [ref=f4e110]: EVIDENCE
|
||||||
|
- heading "본문에 Asset 삽입" [level=3] [ref=f4e111]
|
||||||
|
- paragraph [ref=f4e112]: 목록에서 선택하면 본문 커서 위치에 evidence 구문을 삽입합니다. READY 상태의 Asset만 선택할 수 있습니다.
|
||||||
|
- generic [ref=f4e113]:
|
||||||
|
- generic [ref=f4e114]:
|
||||||
|
- generic [ref=f4e115]: 업로드 종류
|
||||||
|
- combobox "업로드 종류" [ref=f4e116]:
|
||||||
|
- option "이미지" [selected]
|
||||||
|
- option "다이어그램"
|
||||||
|
- option "첨부파일"
|
||||||
|
- button "Asset 업로드" [ref=f4e117]
|
||||||
|
- generic [ref=f4e118]:
|
||||||
|
- search [ref=f4e119]:
|
||||||
|
- generic [ref=f4e120]: Asset 검색
|
||||||
|
- generic [ref=f4e121]:
|
||||||
|
- searchbox "Asset 검색" [ref=f4e122]
|
||||||
|
- button "검색" [ref=f4e123]
|
||||||
|
- generic [ref=f4e124]:
|
||||||
|
- checkbox "삽입할 때 크게 보기 허용" [checked] [ref=f4e125]
|
||||||
|
- generic [ref=f4e126]: 삽입할 때 크게 보기 허용
|
||||||
|
- status [ref=f4e127]: 삽입할 수 있는 Asset 1개
|
||||||
|
- list [ref=f4e128]:
|
||||||
|
- listitem [ref=f4e129]:
|
||||||
|
- button "screenshot-from-2026-08-21-18-04-49-72f1f9c6" [ref=f4e130]
|
||||||
|
- button "삭제" [ref=f4e131]
|
||||||
|
- complementary [ref=f4e132]:
|
||||||
|
- paragraph [ref=f4e133]: WORKING COPY
|
||||||
|
- heading "작업 상태" [level=2] [ref=f4e134]
|
||||||
|
- status "편집 상태" [ref=f4e135]: 저장됨
|
||||||
|
- generic [ref=f4e136]:
|
||||||
|
- generic [ref=f4e137]:
|
||||||
|
- term [ref=f4e138]: 저장 버전
|
||||||
|
- definition [ref=f4e139]: "1"
|
||||||
|
- generic [ref=f4e140]:
|
||||||
|
- term [ref=f4e141]: 종류
|
||||||
|
- definition [ref=f4e142]: CASE
|
||||||
|
- button "저장" [disabled] [ref=f4e143]
|
||||||
|
- navigation "게시까지의 단계" [ref=f4e144]:
|
||||||
|
- list [ref=f4e145]:
|
||||||
|
- listitem [ref=f4e146]:
|
||||||
|
- generic [ref=f4e147]: "1"
|
||||||
|
- link "검증" [ref=f4e148] [cursor=pointer]:
|
||||||
|
- /url: /studio/documents/969cc2c7-be6e-42ca-924a-652c63db9784/validation
|
||||||
|
- listitem [ref=f4e149]:
|
||||||
|
- text: →
|
||||||
|
- generic [ref=f4e150]: "2"
|
||||||
|
- link "미리보기" [ref=f4e151] [cursor=pointer]:
|
||||||
|
- /url: /studio/documents/969cc2c7-be6e-42ca-924a-652c63db9784/preview
|
||||||
|
- listitem [ref=f4e152]:
|
||||||
|
- text: →
|
||||||
|
- generic [ref=f4e153]: "3"
|
||||||
|
- link "게시" [ref=f4e154] [cursor=pointer]:
|
||||||
|
- /url: /studio/documents/969cc2c7-be6e-42ca-924a-652c63db9784/publish
|
||||||
|
- paragraph [ref=f4e155]: 불완전한 초안도 저장할 수 있습니다. 게시 가능 여부는 이후 검증 단계에서 확인합니다.
|
||||||
|
- paragraph [ref=f4e50]: Case 작업본을 만들었습니다.
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
- generic [ref=f5e22]:
|
||||||
|
- link "본문으로 건너뛰기" [ref=f5e23] [cursor=pointer]:
|
||||||
|
- /url: "#main-content"
|
||||||
|
- banner [ref=f5e24]:
|
||||||
|
- generic [ref=f5e25]:
|
||||||
|
- link "TechLog Studio" [ref=f5e26] [cursor=pointer]:
|
||||||
|
- /url: /studio
|
||||||
|
- text: TechLog
|
||||||
|
- generic [ref=f5e27]: Studio
|
||||||
|
- navigation "Studio 주 탐색" [ref=f5e29]:
|
||||||
|
- link "작업본" [ref=f5e30] [cursor=pointer]:
|
||||||
|
- /url: /studio/documents
|
||||||
|
- link "게시 기록" [ref=f5e31] [cursor=pointer]:
|
||||||
|
- /url: /studio/publications
|
||||||
|
- link "새 문서" [ref=f5e32] [cursor=pointer]:
|
||||||
|
- /url: /studio/documents/new
|
||||||
|
- link "주제·프로젝트" [ref=f5e33] [cursor=pointer]:
|
||||||
|
- /url: /studio/taxonomy
|
||||||
|
- link "릴리즈" [ref=f5e34] [cursor=pointer]:
|
||||||
|
- /url: /studio/releases
|
||||||
|
- link "공개 사이트 보기" [ref=f5e35] [cursor=pointer]:
|
||||||
|
- /url: /
|
||||||
|
- button "로그아웃" [ref=f5e36]
|
||||||
|
- main [ref=f5e37]:
|
||||||
|
- generic [ref=f5e38]:
|
||||||
|
- generic [ref=f5e39]:
|
||||||
|
- generic [ref=f5e40]:
|
||||||
|
- paragraph [ref=f5e41]: WORKING COPIES
|
||||||
|
- heading "작업본" [level=1] [ref=f5e42]
|
||||||
|
- paragraph [ref=f5e43]: 세션에 있는 Case, Reference, Question, Decision을 찾고 다음 작업으로 이동합니다.
|
||||||
|
- link "새 문서" [ref=f5e44] [cursor=pointer]:
|
||||||
|
- /url: /studio/documents/new
|
||||||
|
- region "작업본 검색과 필터" [ref=f5e45]:
|
||||||
|
- search [ref=f5e46]:
|
||||||
|
- generic [ref=f5e47]: 검색
|
||||||
|
- generic [ref=f5e48]:
|
||||||
|
- searchbox "검색" [ref=f5e49]
|
||||||
|
- button "검색" [ref=f5e50]
|
||||||
|
- generic [ref=f5e51]:
|
||||||
|
- text: 종류
|
||||||
|
- combobox "종류" [ref=f5e52]:
|
||||||
|
- option "전체" [selected]
|
||||||
|
- option "Case"
|
||||||
|
- option "Reference"
|
||||||
|
- option "Question"
|
||||||
|
- option "Decision"
|
||||||
|
- generic [ref=f5e53]:
|
||||||
|
- text: 상태
|
||||||
|
- combobox "상태" [ref=f5e54]:
|
||||||
|
- option "전체" [selected]
|
||||||
|
- option "게시 전"
|
||||||
|
- option "게시 중"
|
||||||
|
- option "게시 취소"
|
||||||
|
- paragraph [ref=f5e55]:
|
||||||
|
- generic [ref=f5e56]: 2개 표시 중
|
||||||
|
- generic [ref=f5e57]:
|
||||||
|
- article [ref=f5e58]:
|
||||||
|
- paragraph [ref=f5e59]: Case
|
||||||
|
- generic [ref=f5e60]:
|
||||||
|
- heading [level=2] [ref=f5e61]:
|
||||||
|
- link "게시 흐름 확인" [ref=f5e62] [cursor=pointer]:
|
||||||
|
- /url: /studio/documents/b09f168b-b205-478f-b60f-83872d1b4d01/edit
|
||||||
|
- paragraph [ref=f5e63]: 프로젝트 미지정
|
||||||
|
- generic [ref=f5e64]:
|
||||||
|
- generic [ref=f5e65]:
|
||||||
|
- term [ref=f5e66]: 상태
|
||||||
|
- definition [ref=f5e67]: 게시 취소
|
||||||
|
- generic [ref=f5e68]:
|
||||||
|
- term [ref=f5e69]: 다음
|
||||||
|
- definition [ref=f5e70]:
|
||||||
|
- link "검증하기" [ref=f5e71] [cursor=pointer]:
|
||||||
|
- /url: /studio/documents/b09f168b-b205-478f-b60f-83872d1b4d01/validation
|
||||||
|
- generic [ref=f5e72]:
|
||||||
|
- term [ref=f5e73]: 수정
|
||||||
|
- definition [ref=f5e74]:
|
||||||
|
- time [ref=f5e75]: 2026. 8. 21.
|
||||||
|
- button "삭제" [ref=f5e76]
|
||||||
|
- article [ref=f5e77]:
|
||||||
|
- paragraph [ref=f5e78]: Case
|
||||||
|
- generic [ref=f5e79]:
|
||||||
|
- heading [level=2] [ref=f5e80]:
|
||||||
|
- link "브라우저에서 토큰을 어디까지 다뤄야 하는가..?" [ref=f5e81] [cursor=pointer]:
|
||||||
|
- /url: /studio/documents/f363edc8-2c13-4995-acda-934237034a85/edit
|
||||||
|
- paragraph [ref=f5e82]: 프로젝트 미지정
|
||||||
|
- generic [ref=f5e83]:
|
||||||
|
- generic [ref=f5e84]:
|
||||||
|
- term [ref=f5e85]: 상태
|
||||||
|
- definition [ref=f5e86]: 게시 전
|
||||||
|
- generic [ref=f5e87]:
|
||||||
|
- term [ref=f5e88]: 다음
|
||||||
|
- definition [ref=f5e89]:
|
||||||
|
- link "검증하기" [ref=f5e90] [cursor=pointer]:
|
||||||
|
- /url: /studio/documents/f363edc8-2c13-4995-acda-934237034a85/validation
|
||||||
|
- generic [ref=f5e91]:
|
||||||
|
- term [ref=f5e92]: 수정
|
||||||
|
- definition [ref=f5e93]:
|
||||||
|
- time [ref=f5e94]: 2026. 8. 21.
|
||||||
|
- button "삭제" [ref=f5e95]
|
||||||
|
- paragraph [ref=f5e96]: 작업본 제목 없음 을(를) 삭제했습니다.
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
- generic [ref=f6e3]:
|
||||||
|
- link "본문으로 건너뛰기" [ref=f6e4] [cursor=pointer]:
|
||||||
|
- /url: "#main-content"
|
||||||
|
- banner [ref=f6e5]:
|
||||||
|
- generic [ref=f6e6]:
|
||||||
|
- link "TechLog Studio" [ref=f6e7] [cursor=pointer]:
|
||||||
|
- /url: /studio
|
||||||
|
- text: TechLog
|
||||||
|
- generic [ref=f6e8]: Studio
|
||||||
|
- navigation "Studio 주 탐색" [ref=f6e10]:
|
||||||
|
- link "작업본" [ref=f6e11] [cursor=pointer]:
|
||||||
|
- /url: /studio/documents
|
||||||
|
- link "게시 기록" [ref=f6e12] [cursor=pointer]:
|
||||||
|
- /url: /studio/publications
|
||||||
|
- link "새 문서" [ref=f6e13] [cursor=pointer]:
|
||||||
|
- /url: /studio/documents/new
|
||||||
|
- link "주제·프로젝트" [ref=f6e14] [cursor=pointer]:
|
||||||
|
- /url: /studio/taxonomy
|
||||||
|
- link "릴리즈" [ref=f6e15] [cursor=pointer]:
|
||||||
|
- /url: /studio/releases
|
||||||
|
- link "공개 사이트 보기" [ref=f6e16] [cursor=pointer]:
|
||||||
|
- /url: /
|
||||||
|
- button "로그아웃" [ref=f6e17]
|
||||||
|
- main [ref=f6e18]:
|
||||||
|
- generic [ref=f6e19]:
|
||||||
|
- generic [ref=f6e20]:
|
||||||
|
- paragraph [ref=f6e21]: NEW WORKING COPY
|
||||||
|
- heading "새 문서" [level=1] [ref=f6e22]
|
||||||
|
- paragraph [ref=f6e23]: 목적에 맞는 기록 종류를 선택하면 빈 작업본을 만들고 바로 편집을 시작합니다.
|
||||||
|
- generic [ref=f6e24]:
|
||||||
|
- group "문서 종류" [ref=f6e25]:
|
||||||
|
- generic [ref=f6e27] [cursor=pointer]:
|
||||||
|
- radio "Case 문제를 재현하고 검증한 결론을 기록합니다. 문제 · 결론 · 환경 · 재현 · 본문" [ref=f6e28]
|
||||||
|
- strong [ref=f6e29]: Case
|
||||||
|
- generic [ref=f6e30]: 문제를 재현하고 검증한 결론을 기록합니다.
|
||||||
|
- generic [ref=f6e31]: 문제 · 결론 · 환경 · 재현 · 본문
|
||||||
|
- generic [ref=f6e32] [cursor=pointer]:
|
||||||
|
- radio "Reference 반복해서 적용할 기술 기준을 정리합니다. 목적 · 규칙 · 적용 조건 · 예외 · 예시" [ref=f6e33]
|
||||||
|
- strong [ref=f6e34]: Reference
|
||||||
|
- generic [ref=f6e35]: 반복해서 적용할 기술 기준을 정리합니다.
|
||||||
|
- generic [ref=f6e36]: 목적 · 규칙 · 적용 조건 · 예외 · 예시
|
||||||
|
- generic [ref=f6e37] [cursor=pointer]:
|
||||||
|
- radio "Question 아직 닫히지 않은 판단과 다음 검증을 관리합니다. 상태 · 사실 · 가정 · 미지수 · 선택지" [checked] [active] [ref=f6e38]
|
||||||
|
- strong [ref=f6e39]: Question
|
||||||
|
- generic [ref=f6e40]: 아직 닫히지 않은 판단과 다음 검증을 관리합니다.
|
||||||
|
- generic [ref=f6e41]: 상태 · 사실 · 가정 · 미지수 · 선택지
|
||||||
|
- generic [ref=f6e42] [cursor=pointer]:
|
||||||
|
- radio "Decision 프로젝트가 선택한 방향과 그 근거·영향을 기록합니다. 상태 · 결정일 · 결정문 · 판단 이유 · 영향 · 근거" [ref=f6e43]
|
||||||
|
- strong [ref=f6e44]: Decision
|
||||||
|
- generic [ref=f6e45]: 프로젝트가 선택한 방향과 그 근거·영향을 기록합니다.
|
||||||
|
- generic [ref=f6e46]: 상태 · 결정일 · 결정문 · 판단 이유 · 영향 · 근거
|
||||||
|
- generic [ref=f6e47]:
|
||||||
|
- button "작업본 만들기" [ref=f6e48]
|
||||||
|
- paragraph [ref=f6e49]: 이 화면의 작업본은 현재 Studio 세션에서만 유지됩니다.
|
||||||
|
- paragraph [ref=f6e50]
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
- generic [ref=f6e3]:
|
||||||
|
- link "본문으로 건너뛰기" [ref=f6e4] [cursor=pointer]:
|
||||||
|
- /url: "#main-content"
|
||||||
|
- banner [ref=f6e5]:
|
||||||
|
- generic [ref=f6e6]:
|
||||||
|
- link "TechLog Studio" [ref=f6e7] [cursor=pointer]:
|
||||||
|
- /url: /studio
|
||||||
|
- text: TechLog
|
||||||
|
- generic [ref=f6e8]: Studio
|
||||||
|
- navigation "Studio 주 탐색" [ref=f6e10]:
|
||||||
|
- link "작업본" [ref=f6e11] [cursor=pointer]:
|
||||||
|
- /url: /studio/documents
|
||||||
|
- link "게시 기록" [ref=f6e12] [cursor=pointer]:
|
||||||
|
- /url: /studio/publications
|
||||||
|
- link "새 문서" [ref=f6e13] [cursor=pointer]:
|
||||||
|
- /url: /studio/documents/new
|
||||||
|
- link "주제·프로젝트" [ref=f6e14] [cursor=pointer]:
|
||||||
|
- /url: /studio/taxonomy
|
||||||
|
- link "릴리즈" [ref=f6e15] [cursor=pointer]:
|
||||||
|
- /url: /studio/releases
|
||||||
|
- link "공개 사이트 보기" [ref=f6e16] [cursor=pointer]:
|
||||||
|
- /url: /
|
||||||
|
- button "로그아웃" [ref=f6e17]
|
||||||
|
- main [ref=f6e18]:
|
||||||
|
- generic [ref=f6e51]:
|
||||||
|
- tablist "문서 편집 화면" [ref=f6e52]:
|
||||||
|
- tab "편집" [selected] [ref=f6e53]
|
||||||
|
- tab "즉시 미리보기" [ref=f6e54]
|
||||||
|
- generic [ref=f6e55]:
|
||||||
|
- tabpanel "편집" [ref=f6e57]:
|
||||||
|
- generic [ref=f6e58]:
|
||||||
|
- paragraph [ref=f6e59]: QUESTION · VERSION 1
|
||||||
|
- heading "문서 편집" [level=1] [ref=f6e60]
|
||||||
|
- paragraph [ref=f6e61]: 제목 없는 작업본
|
||||||
|
- region [ref=f6e62]:
|
||||||
|
- generic [ref=f6e63]:
|
||||||
|
- paragraph [ref=f6e64]: DOCUMENT
|
||||||
|
- heading "기본 정보" [level=2] [ref=f6e65]
|
||||||
|
- generic [ref=f6e66]:
|
||||||
|
- generic [ref=f6e67]:
|
||||||
|
- generic [ref=f6e68]: 제목
|
||||||
|
- textbox "제목" [ref=f6e69]
|
||||||
|
- generic [ref=f6e70]:
|
||||||
|
- generic [ref=f6e71]: slug
|
||||||
|
- textbox "slug" [ref=f6e72]:
|
||||||
|
- /placeholder: 비우면 제목에서 만듭니다 (영문 소문자·숫자·하이픈)
|
||||||
|
- generic [ref=f6e73]:
|
||||||
|
- generic [ref=f6e74]: 요약
|
||||||
|
- textbox "요약" [ref=f6e75]
|
||||||
|
- generic [ref=f6e76]:
|
||||||
|
- generic [ref=f6e77]: Topic
|
||||||
|
- combobox "Topic" [ref=f6e78]:
|
||||||
|
- option "선택하지 않음" [selected]
|
||||||
|
- option "OAuth/OIDC 인증 경계"
|
||||||
|
- generic [ref=f6e79]:
|
||||||
|
- generic [ref=f6e80]: Project
|
||||||
|
- combobox "Project" [ref=f6e81]:
|
||||||
|
- option "미지정" [selected]
|
||||||
|
- option "Backend Clean Architecture"
|
||||||
|
- option "KeyCloak Patterns"
|
||||||
|
- option "Liner N + 1문제"
|
||||||
|
- group "관계" [ref=f6e82]:
|
||||||
|
- paragraph [ref=f6e84]: 연결한 공개 기록이 없습니다.
|
||||||
|
- button "관계 추가" [ref=f6e85]
|
||||||
|
- region [ref=f6e86]:
|
||||||
|
- generic [ref=f6e87]:
|
||||||
|
- paragraph [ref=f6e88]: QUESTION
|
||||||
|
- heading "판단과 다음 검증" [level=2] [ref=f6e89]
|
||||||
|
- generic [ref=f6e90]:
|
||||||
|
- generic [ref=f6e91]: 질문 상태
|
||||||
|
- combobox "질문 상태" [ref=f6e92]:
|
||||||
|
- option "아직 정하지 않음"
|
||||||
|
- option "OPEN" [selected]
|
||||||
|
- option "RESOLVED"
|
||||||
|
- group "사실" [ref=f6e93]:
|
||||||
|
- paragraph [ref=f6e95]: 아직 입력한 항목이 없습니다.
|
||||||
|
- button "사실 추가" [ref=f6e96]
|
||||||
|
- group "가정" [ref=f6e97]:
|
||||||
|
- paragraph [ref=f6e99]: 아직 입력한 항목이 없습니다.
|
||||||
|
- button "가정 추가" [ref=f6e100]
|
||||||
|
- group "미지수" [ref=f6e101]:
|
||||||
|
- paragraph [ref=f6e103]: 아직 입력한 항목이 없습니다.
|
||||||
|
- button "미지수 추가" [ref=f6e104]
|
||||||
|
- group "제약" [ref=f6e105]:
|
||||||
|
- paragraph [ref=f6e107]: 아직 입력한 항목이 없습니다.
|
||||||
|
- button "제약 추가" [ref=f6e108]
|
||||||
|
- group "선택지" [ref=f6e109]:
|
||||||
|
- paragraph [ref=f6e111]: 아직 입력한 선택지가 없습니다.
|
||||||
|
- button "선택지 추가" [ref=f6e112]
|
||||||
|
- generic [ref=f6e113]:
|
||||||
|
- generic [ref=f6e114]: 다음 검증
|
||||||
|
- textbox "다음 검증" [ref=f6e115]
|
||||||
|
- complementary [ref=f6e116]:
|
||||||
|
- paragraph [ref=f6e117]: WORKING COPY
|
||||||
|
- heading "작업 상태" [level=2] [ref=f6e118]
|
||||||
|
- status "편집 상태" [ref=f6e119]: 저장됨
|
||||||
|
- generic [ref=f6e120]:
|
||||||
|
- generic [ref=f6e121]:
|
||||||
|
- term [ref=f6e122]: 저장 버전
|
||||||
|
- definition [ref=f6e123]: "1"
|
||||||
|
- generic [ref=f6e124]:
|
||||||
|
- term [ref=f6e125]: 종류
|
||||||
|
- definition [ref=f6e126]: QUESTION
|
||||||
|
- button "저장" [disabled] [ref=f6e127]
|
||||||
|
- navigation "게시까지의 단계" [ref=f6e128]:
|
||||||
|
- list [ref=f6e129]:
|
||||||
|
- listitem [ref=f6e130]:
|
||||||
|
- generic [ref=f6e131]: "1"
|
||||||
|
- link "검증" [ref=f6e132] [cursor=pointer]:
|
||||||
|
- /url: /studio/documents/f54170bb-e1a2-466d-9d1c-39563e03266c/validation
|
||||||
|
- listitem [ref=f6e133]:
|
||||||
|
- text: →
|
||||||
|
- generic [ref=f6e134]: "2"
|
||||||
|
- link "미리보기" [ref=f6e135] [cursor=pointer]:
|
||||||
|
- /url: /studio/documents/f54170bb-e1a2-466d-9d1c-39563e03266c/preview
|
||||||
|
- listitem [ref=f6e136]:
|
||||||
|
- text: →
|
||||||
|
- generic [ref=f6e137]: "3"
|
||||||
|
- link "게시" [ref=f6e138] [cursor=pointer]:
|
||||||
|
- /url: /studio/documents/f54170bb-e1a2-466d-9d1c-39563e03266c/publish
|
||||||
|
- paragraph [ref=f6e139]: 불완전한 초안도 저장할 수 있습니다. 게시 가능 여부는 이후 검증 단계에서 확인합니다.
|
||||||
|
- paragraph [ref=f6e50]: Question 작업본을 만들었습니다.
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
- generic [ref=f7e3]:
|
||||||
|
- link "본문으로 건너뛰기" [ref=f7e4] [cursor=pointer]:
|
||||||
|
- /url: "#main-content"
|
||||||
|
- banner [ref=f7e5]:
|
||||||
|
- generic [ref=f7e6]:
|
||||||
|
- link "TechLog Studio" [ref=f7e7] [cursor=pointer]:
|
||||||
|
- /url: /studio
|
||||||
|
- text: TechLog
|
||||||
|
- generic [ref=f7e8]: Studio
|
||||||
|
- navigation "Studio 주 탐색" [ref=f7e10]:
|
||||||
|
- link "작업본" [ref=f7e11] [cursor=pointer]:
|
||||||
|
- /url: /studio/documents
|
||||||
|
- link "게시 기록" [ref=f7e12] [cursor=pointer]:
|
||||||
|
- /url: /studio/publications
|
||||||
|
- link "새 문서" [ref=f7e13] [cursor=pointer]:
|
||||||
|
- /url: /studio/documents/new
|
||||||
|
- link "주제·프로젝트" [ref=f7e14] [cursor=pointer]:
|
||||||
|
- /url: /studio/taxonomy
|
||||||
|
- link "릴리즈" [ref=f7e15] [cursor=pointer]:
|
||||||
|
- /url: /studio/releases
|
||||||
|
- link "공개 사이트 보기" [ref=f7e16] [cursor=pointer]:
|
||||||
|
- /url: /
|
||||||
|
- button "로그아웃" [ref=f7e17]
|
||||||
|
- main [ref=f7e18]:
|
||||||
|
- generic [ref=f7e19]:
|
||||||
|
- generic [ref=f7e20]:
|
||||||
|
- generic [ref=f7e21]:
|
||||||
|
- paragraph [ref=f7e22]: WORKING COPIES
|
||||||
|
- heading "작업본" [level=1] [ref=f7e23]
|
||||||
|
- paragraph [ref=f7e24]: 세션에 있는 Case, Reference, Question, Decision을 찾고 다음 작업으로 이동합니다.
|
||||||
|
- link "새 문서" [ref=f7e25] [cursor=pointer]:
|
||||||
|
- /url: /studio/documents/new
|
||||||
|
- region "작업본 검색과 필터" [ref=f7e26]:
|
||||||
|
- search [ref=f7e27]:
|
||||||
|
- generic [ref=f7e28]: 검색
|
||||||
|
- generic [ref=f7e29]:
|
||||||
|
- searchbox "검색" [ref=f7e30]
|
||||||
|
- button "검색" [ref=f7e31]
|
||||||
|
- generic [ref=f7e32]:
|
||||||
|
- text: 종류
|
||||||
|
- combobox "종류" [ref=f7e33]:
|
||||||
|
- option "전체" [selected]
|
||||||
|
- option "Case"
|
||||||
|
- option "Reference"
|
||||||
|
- option "Question"
|
||||||
|
- option "Decision"
|
||||||
|
- generic [ref=f7e34]:
|
||||||
|
- text: 상태
|
||||||
|
- combobox "상태" [ref=f7e35]:
|
||||||
|
- option "전체" [selected]
|
||||||
|
- option "게시 전"
|
||||||
|
- option "게시 중"
|
||||||
|
- option "게시 취소"
|
||||||
|
- paragraph [ref=f7e36]:
|
||||||
|
- generic [ref=f7e37]: 2개 표시 중
|
||||||
|
- generic [ref=f7e38]:
|
||||||
|
- article [ref=f7e39]:
|
||||||
|
- paragraph [ref=f7e40]: Case
|
||||||
|
- generic [ref=f7e41]:
|
||||||
|
- heading [level=2] [ref=f7e42]:
|
||||||
|
- link "게시 흐름 확인" [ref=f7e43] [cursor=pointer]:
|
||||||
|
- /url: /studio/documents/b09f168b-b205-478f-b60f-83872d1b4d01/edit
|
||||||
|
- paragraph [ref=f7e44]: 프로젝트 미지정
|
||||||
|
- generic [ref=f7e45]:
|
||||||
|
- generic [ref=f7e46]:
|
||||||
|
- term [ref=f7e47]: 상태
|
||||||
|
- definition [ref=f7e48]: 게시 취소
|
||||||
|
- generic [ref=f7e49]:
|
||||||
|
- term [ref=f7e50]: 다음
|
||||||
|
- definition [ref=f7e51]:
|
||||||
|
- link "검증하기" [ref=f7e52] [cursor=pointer]:
|
||||||
|
- /url: /studio/documents/b09f168b-b205-478f-b60f-83872d1b4d01/validation
|
||||||
|
- generic [ref=f7e53]:
|
||||||
|
- term [ref=f7e54]: 수정
|
||||||
|
- definition [ref=f7e55]:
|
||||||
|
- time [ref=f7e56]: 2026. 8. 21.
|
||||||
|
- button "삭제" [ref=f7e57]
|
||||||
|
- article [ref=f7e58]:
|
||||||
|
- paragraph [ref=f7e59]: Case
|
||||||
|
- generic [ref=f7e60]:
|
||||||
|
- heading [level=2] [ref=f7e61]:
|
||||||
|
- link "브라우저에서 토큰을 어디까지 다뤄야 하는가..?" [ref=f7e62] [cursor=pointer]:
|
||||||
|
- /url: /studio/documents/f363edc8-2c13-4995-acda-934237034a85/edit
|
||||||
|
- paragraph [ref=f7e63]: 프로젝트 미지정
|
||||||
|
- generic [ref=f7e64]:
|
||||||
|
- generic [ref=f7e65]:
|
||||||
|
- term [ref=f7e66]: 상태
|
||||||
|
- definition [ref=f7e67]: 게시 전
|
||||||
|
- generic [ref=f7e68]:
|
||||||
|
- term [ref=f7e69]: 다음
|
||||||
|
- definition [ref=f7e70]:
|
||||||
|
- link "검증하기" [ref=f7e71] [cursor=pointer]:
|
||||||
|
- /url: /studio/documents/f363edc8-2c13-4995-acda-934237034a85/validation
|
||||||
|
- generic [ref=f7e72]:
|
||||||
|
- term [ref=f7e73]: 수정
|
||||||
|
- definition [ref=f7e74]:
|
||||||
|
- time [ref=f7e75]: 2026. 8. 21.
|
||||||
|
- button "삭제" [ref=f7e76]
|
||||||
|
- paragraph [ref=f7e77]: 작업본 제목 없음 을(를) 삭제했습니다.
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
- generic [ref=f8e74]:
|
||||||
|
- link "본문으로 건너뛰기" [ref=f8e75] [cursor=pointer]:
|
||||||
|
- /url: "#main-content"
|
||||||
|
- banner [ref=f8e76]:
|
||||||
|
- generic [ref=f8e77]:
|
||||||
|
- link "TechLog Studio" [ref=f8e78] [cursor=pointer]:
|
||||||
|
- /url: /studio
|
||||||
|
- text: TechLog
|
||||||
|
- generic [ref=f8e79]: Studio
|
||||||
|
- navigation "Studio 주 탐색" [ref=f8e81]:
|
||||||
|
- link "작업본" [ref=f8e82] [cursor=pointer]:
|
||||||
|
- /url: /studio/documents
|
||||||
|
- link "게시 기록" [ref=f8e83] [cursor=pointer]:
|
||||||
|
- /url: /studio/publications
|
||||||
|
- link "새 문서" [ref=f8e84] [cursor=pointer]:
|
||||||
|
- /url: /studio/documents/new
|
||||||
|
- link "주제·프로젝트" [ref=f8e85] [cursor=pointer]:
|
||||||
|
- /url: /studio/taxonomy
|
||||||
|
- link "릴리즈" [ref=f8e86] [cursor=pointer]:
|
||||||
|
- /url: /studio/releases
|
||||||
|
- link "공개 사이트 보기" [ref=f8e87] [cursor=pointer]:
|
||||||
|
- /url: /
|
||||||
|
- button "로그아웃" [ref=f8e88]
|
||||||
|
- main [ref=f8e89]:
|
||||||
|
- generic [ref=f8e1]:
|
||||||
|
- generic [ref=f8e3]:
|
||||||
|
- paragraph [ref=f8e4]: TAXONOMY
|
||||||
|
- heading "주제와 프로젝트" [level=1] [ref=f8e5]
|
||||||
|
- paragraph [ref=f8e6]: 문서를 게시하려면 주제가 필요합니다. 여기서 만들고 정리합니다.
|
||||||
|
- region "주제 만들기" [ref=f8e7]:
|
||||||
|
- generic [ref=f8e8]:
|
||||||
|
- generic [ref=f8e9]: 새 주제
|
||||||
|
- generic [ref=f8e10]:
|
||||||
|
- textbox "새 주제" [ref=f8e11]:
|
||||||
|
- /placeholder: 주제 이름
|
||||||
|
- textbox "주제 slug" [ref=f8e12]:
|
||||||
|
- /placeholder: slug (비우면 이름에서 생성)
|
||||||
|
- button "추가" [ref=f8e13]
|
||||||
|
- generic [ref=f8e14]:
|
||||||
|
- generic [ref=f8e15]: 새 프로젝트
|
||||||
|
- generic [ref=f8e16]:
|
||||||
|
- textbox "새 프로젝트" [ref=f8e17]:
|
||||||
|
- /placeholder: 프로젝트 이름
|
||||||
|
- button "추가" [ref=f8e18]
|
||||||
|
- paragraph [ref=f8e90]: 2개의 주제
|
||||||
|
- generic [ref=f8e91]:
|
||||||
|
- article [ref=f8e92]:
|
||||||
|
- paragraph [ref=f8e93]: TOPIC
|
||||||
|
- generic [ref=f8e94]:
|
||||||
|
- heading "삭제 시험 주제" [level=2] [ref=f8e95]
|
||||||
|
- paragraph [ref=f8e96]: sakje-siheom-juje
|
||||||
|
- generic [ref=f8e98]:
|
||||||
|
- term [ref=f8e99]: 상태
|
||||||
|
- definition [ref=f8e100]: 사용 중
|
||||||
|
- button "삭제" [ref=f8e101]
|
||||||
|
- article [ref=f8e102]:
|
||||||
|
- paragraph [ref=f8e103]: TOPIC
|
||||||
|
- generic [ref=f8e104]:
|
||||||
|
- heading "OAuth/OIDC 인증 경계" [level=2] [ref=f8e105]
|
||||||
|
- paragraph [ref=f8e106]: oauth-oidc-auth-boundary
|
||||||
|
- generic [ref=f8e108]:
|
||||||
|
- term [ref=f8e109]: 상태
|
||||||
|
- definition [ref=f8e110]: 사용 중
|
||||||
|
- button "삭제" [ref=f8e111]
|
||||||
|
- paragraph [ref=f8e112]: 3개의 프로젝트
|
||||||
|
- generic [ref=f8e113]:
|
||||||
|
- article [ref=f8e114]:
|
||||||
|
- paragraph [ref=f8e115]: PROJECT
|
||||||
|
- generic [ref=f8e116]:
|
||||||
|
- heading "Liner N + 1문제" [level=2] [ref=f8e117]
|
||||||
|
- paragraph [ref=f8e118]: 목표 미지정
|
||||||
|
- generic [ref=f8e119]:
|
||||||
|
- generic [ref=f8e120]:
|
||||||
|
- term [ref=f8e121]: 단계
|
||||||
|
- definition [ref=f8e122]: RESEARCH
|
||||||
|
- generic [ref=f8e123]:
|
||||||
|
- term [ref=f8e124]: 공개
|
||||||
|
- definition [ref=f8e125]: PRIVATE
|
||||||
|
- button "삭제" [ref=f8e126]
|
||||||
|
- article [ref=f8e127]:
|
||||||
|
- paragraph [ref=f8e128]: PROJECT
|
||||||
|
- generic [ref=f8e129]:
|
||||||
|
- heading "KeyCloak Patterns" [level=2] [ref=f8e130]
|
||||||
|
- paragraph [ref=f8e131]: 목표 미지정
|
||||||
|
- generic [ref=f8e132]:
|
||||||
|
- generic [ref=f8e133]:
|
||||||
|
- term [ref=f8e134]: 단계
|
||||||
|
- definition [ref=f8e135]: RESEARCH
|
||||||
|
- generic [ref=f8e136]:
|
||||||
|
- term [ref=f8e137]: 공개
|
||||||
|
- definition [ref=f8e138]: PRIVATE
|
||||||
|
- button "삭제" [ref=f8e139]
|
||||||
|
- article [ref=f8e140]:
|
||||||
|
- paragraph [ref=f8e141]: PROJECT
|
||||||
|
- generic [ref=f8e142]:
|
||||||
|
- heading "Backend Clean Architecture" [level=2] [ref=f8e143]
|
||||||
|
- paragraph [ref=f8e144]: 목표 미지정
|
||||||
|
- generic [ref=f8e145]:
|
||||||
|
- generic [ref=f8e146]:
|
||||||
|
- term [ref=f8e147]: 단계
|
||||||
|
- definition [ref=f8e148]: RESEARCH
|
||||||
|
- generic [ref=f8e149]:
|
||||||
|
- term [ref=f8e150]: 공개
|
||||||
|
- definition [ref=f8e151]: PRIVATE
|
||||||
|
- button "삭제" [ref=f8e152]
|
||||||
|
- paragraph [ref=f8e153]: 주제 삭제 시험 주제 을(를) 만들었습니다.
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
- generic [ref=f8e74]:
|
||||||
|
- link "본문으로 건너뛰기" [ref=f8e75] [cursor=pointer]:
|
||||||
|
- /url: "#main-content"
|
||||||
|
- banner [ref=f8e76]:
|
||||||
|
- generic [ref=f8e77]:
|
||||||
|
- link "TechLog Studio" [ref=f8e78] [cursor=pointer]:
|
||||||
|
- /url: /studio
|
||||||
|
- text: TechLog
|
||||||
|
- generic [ref=f8e79]: Studio
|
||||||
|
- navigation "Studio 주 탐색" [ref=f8e81]:
|
||||||
|
- link "작업본" [ref=f8e82] [cursor=pointer]:
|
||||||
|
- /url: /studio/documents
|
||||||
|
- link "게시 기록" [ref=f8e83] [cursor=pointer]:
|
||||||
|
- /url: /studio/publications
|
||||||
|
- link "새 문서" [ref=f8e84] [cursor=pointer]:
|
||||||
|
- /url: /studio/documents/new
|
||||||
|
- link "주제·프로젝트" [ref=f8e85] [cursor=pointer]:
|
||||||
|
- /url: /studio/taxonomy
|
||||||
|
- link "릴리즈" [ref=f8e86] [cursor=pointer]:
|
||||||
|
- /url: /studio/releases
|
||||||
|
- link "공개 사이트 보기" [ref=f8e87] [cursor=pointer]:
|
||||||
|
- /url: /
|
||||||
|
- button "로그아웃" [ref=f8e88]
|
||||||
|
- main [ref=f8e89]:
|
||||||
|
- generic [ref=f8e1]:
|
||||||
|
- generic [ref=f8e3]:
|
||||||
|
- paragraph [ref=f8e4]: TAXONOMY
|
||||||
|
- heading "주제와 프로젝트" [level=1] [ref=f8e5]
|
||||||
|
- paragraph [ref=f8e6]: 문서를 게시하려면 주제가 필요합니다. 여기서 만들고 정리합니다.
|
||||||
|
- region "주제 만들기" [ref=f8e7]:
|
||||||
|
- generic [ref=f8e8]:
|
||||||
|
- generic [ref=f8e9]: 새 주제
|
||||||
|
- generic [ref=f8e10]:
|
||||||
|
- textbox "새 주제" [ref=f8e11]:
|
||||||
|
- /placeholder: 주제 이름
|
||||||
|
- textbox "주제 slug" [ref=f8e12]:
|
||||||
|
- /placeholder: slug (비우면 이름에서 생성)
|
||||||
|
- button "추가" [ref=f8e13]
|
||||||
|
- generic [ref=f8e14]:
|
||||||
|
- generic [ref=f8e15]: 새 프로젝트
|
||||||
|
- generic [ref=f8e16]:
|
||||||
|
- textbox "새 프로젝트" [ref=f8e17]:
|
||||||
|
- /placeholder: 프로젝트 이름
|
||||||
|
- button "추가" [ref=f8e18]
|
||||||
|
- paragraph [ref=f8e154]: 1개의 주제
|
||||||
|
- article [ref=f8e156]:
|
||||||
|
- paragraph [ref=f8e157]: TOPIC
|
||||||
|
- generic [ref=f8e158]:
|
||||||
|
- heading "OAuth/OIDC 인증 경계" [level=2] [ref=f8e159]
|
||||||
|
- paragraph [ref=f8e160]: oauth-oidc-auth-boundary
|
||||||
|
- generic [ref=f8e162]:
|
||||||
|
- term [ref=f8e163]: 상태
|
||||||
|
- definition [ref=f8e164]: 사용 중
|
||||||
|
- button "삭제" [ref=f8e165]
|
||||||
|
- paragraph [ref=f8e166]: 3개의 프로젝트
|
||||||
|
- generic [ref=f8e167]:
|
||||||
|
- article [ref=f8e168]:
|
||||||
|
- paragraph [ref=f8e169]: PROJECT
|
||||||
|
- generic [ref=f8e170]:
|
||||||
|
- heading "Liner N + 1문제" [level=2] [ref=f8e171]
|
||||||
|
- paragraph [ref=f8e172]: 목표 미지정
|
||||||
|
- generic [ref=f8e173]:
|
||||||
|
- generic [ref=f8e174]:
|
||||||
|
- term [ref=f8e175]: 단계
|
||||||
|
- definition [ref=f8e176]: RESEARCH
|
||||||
|
- generic [ref=f8e177]:
|
||||||
|
- term [ref=f8e178]: 공개
|
||||||
|
- definition [ref=f8e179]: PRIVATE
|
||||||
|
- button "삭제" [ref=f8e180]
|
||||||
|
- article [ref=f8e181]:
|
||||||
|
- paragraph [ref=f8e182]: PROJECT
|
||||||
|
- generic [ref=f8e183]:
|
||||||
|
- heading "KeyCloak Patterns" [level=2] [ref=f8e184]
|
||||||
|
- paragraph [ref=f8e185]: 목표 미지정
|
||||||
|
- generic [ref=f8e186]:
|
||||||
|
- generic [ref=f8e187]:
|
||||||
|
- term [ref=f8e188]: 단계
|
||||||
|
- definition [ref=f8e189]: RESEARCH
|
||||||
|
- generic [ref=f8e190]:
|
||||||
|
- term [ref=f8e191]: 공개
|
||||||
|
- definition [ref=f8e192]: PRIVATE
|
||||||
|
- button "삭제" [ref=f8e193]
|
||||||
|
- article [ref=f8e194]:
|
||||||
|
- paragraph [ref=f8e195]: PROJECT
|
||||||
|
- generic [ref=f8e196]:
|
||||||
|
- heading "Backend Clean Architecture" [level=2] [ref=f8e197]
|
||||||
|
- paragraph [ref=f8e198]: 목표 미지정
|
||||||
|
- generic [ref=f8e199]:
|
||||||
|
- generic [ref=f8e200]:
|
||||||
|
- term [ref=f8e201]: 단계
|
||||||
|
- definition [ref=f8e202]: RESEARCH
|
||||||
|
- generic [ref=f8e203]:
|
||||||
|
- term [ref=f8e204]: 공개
|
||||||
|
- definition [ref=f8e205]: PRIVATE
|
||||||
|
- button "삭제" [ref=f8e206]
|
||||||
|
- paragraph [ref=f8e153]: 주제 삭제 시험 주제 을(를) 삭제했습니다.
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
- generic [ref=f9e3]:
|
||||||
|
- link "본문으로 건너뛰기" [ref=f9e4] [cursor=pointer]:
|
||||||
|
- /url: "#main-content"
|
||||||
|
- banner [ref=f9e5]:
|
||||||
|
- generic [ref=f9e6]:
|
||||||
|
- link "TechLog Studio" [ref=f9e7] [cursor=pointer]:
|
||||||
|
- /url: /studio
|
||||||
|
- text: TechLog
|
||||||
|
- generic [ref=f9e8]: Studio
|
||||||
|
- navigation "Studio 주 탐색" [ref=f9e10]:
|
||||||
|
- link "작업본" [ref=f9e11] [cursor=pointer]:
|
||||||
|
- /url: /studio/documents
|
||||||
|
- link "게시 기록" [ref=f9e12] [cursor=pointer]:
|
||||||
|
- /url: /studio/publications
|
||||||
|
- link "새 문서" [ref=f9e13] [cursor=pointer]:
|
||||||
|
- /url: /studio/documents/new
|
||||||
|
- link "주제·프로젝트" [ref=f9e14] [cursor=pointer]:
|
||||||
|
- /url: /studio/taxonomy
|
||||||
|
- link "릴리즈" [ref=f9e15] [cursor=pointer]:
|
||||||
|
- /url: /studio/releases
|
||||||
|
- link "공개 사이트 보기" [ref=f9e16] [cursor=pointer]:
|
||||||
|
- /url: /
|
||||||
|
- button "로그아웃" [ref=f9e17]
|
||||||
|
- main [ref=f9e18]:
|
||||||
|
- generic [ref=f9e19]:
|
||||||
|
- generic [ref=f9e20]:
|
||||||
|
- generic [ref=f9e21]:
|
||||||
|
- paragraph [ref=f9e22]: WORKING COPIES
|
||||||
|
- heading "작업본" [level=1] [ref=f9e23]
|
||||||
|
- paragraph [ref=f9e24]: 세션에 있는 Case, Reference, Question, Decision을 찾고 다음 작업으로 이동합니다.
|
||||||
|
- link "새 문서" [ref=f9e25] [cursor=pointer]:
|
||||||
|
- /url: /studio/documents/new
|
||||||
|
- region "작업본 검색과 필터" [ref=f9e26]:
|
||||||
|
- search [ref=f9e27]:
|
||||||
|
- generic [ref=f9e28]: 검색
|
||||||
|
- generic [ref=f9e29]:
|
||||||
|
- searchbox "검색" [ref=f9e30]
|
||||||
|
- button "검색" [ref=f9e31]
|
||||||
|
- generic [ref=f9e32]:
|
||||||
|
- text: 종류
|
||||||
|
- combobox "종류" [ref=f9e33]:
|
||||||
|
- option "전체" [selected]
|
||||||
|
- option "Case"
|
||||||
|
- option "Reference"
|
||||||
|
- option "Question"
|
||||||
|
- option "Decision"
|
||||||
|
- generic [ref=f9e34]:
|
||||||
|
- text: 상태
|
||||||
|
- combobox "상태" [ref=f9e35]:
|
||||||
|
- option "전체" [selected]
|
||||||
|
- option "게시 전"
|
||||||
|
- option "게시 중"
|
||||||
|
- option "게시 취소"
|
||||||
|
- paragraph [ref=f9e36]:
|
||||||
|
- generic [ref=f9e37]: 2개 표시 중
|
||||||
|
- alert [ref=f9e38]: 삭제하지 못했습니다. 게시 중이거나, 이 기록을 참조하는 곳이 있거나, 다른 곳에서 먼저 수정되었을 수 있습니다.
|
||||||
|
- generic [ref=f9e39]:
|
||||||
|
- article [ref=f9e40]:
|
||||||
|
- paragraph [ref=f9e41]: Case
|
||||||
|
- generic [ref=f9e42]:
|
||||||
|
- heading [level=2] [ref=f9e43]:
|
||||||
|
- link "게시 흐름 확인" [ref=f9e44] [cursor=pointer]:
|
||||||
|
- /url: /studio/documents/b09f168b-b205-478f-b60f-83872d1b4d01/edit
|
||||||
|
- paragraph [ref=f9e45]: 프로젝트 미지정
|
||||||
|
- generic [ref=f9e46]:
|
||||||
|
- generic [ref=f9e47]:
|
||||||
|
- term [ref=f9e48]: 상태
|
||||||
|
- definition [ref=f9e49]: 게시 취소
|
||||||
|
- generic [ref=f9e50]:
|
||||||
|
- term [ref=f9e51]: 다음
|
||||||
|
- definition [ref=f9e52]:
|
||||||
|
- link "검증하기" [ref=f9e53] [cursor=pointer]:
|
||||||
|
- /url: /studio/documents/b09f168b-b205-478f-b60f-83872d1b4d01/validation
|
||||||
|
- generic [ref=f9e54]:
|
||||||
|
- term [ref=f9e55]: 수정
|
||||||
|
- definition [ref=f9e56]:
|
||||||
|
- time [ref=f9e57]: 2026. 8. 21.
|
||||||
|
- button "삭제" [ref=f9e58]
|
||||||
|
- article [ref=f9e59]:
|
||||||
|
- paragraph [ref=f9e60]: Case
|
||||||
|
- generic [ref=f9e61]:
|
||||||
|
- heading [level=2] [ref=f9e62]:
|
||||||
|
- link "브라우저에서 토큰을 어디까지 다뤄야 하는가..?" [ref=f9e63] [cursor=pointer]:
|
||||||
|
- /url: /studio/documents/f363edc8-2c13-4995-acda-934237034a85/edit
|
||||||
|
- paragraph [ref=f9e64]: 프로젝트 미지정
|
||||||
|
- generic [ref=f9e65]:
|
||||||
|
- generic [ref=f9e66]:
|
||||||
|
- term [ref=f9e67]: 상태
|
||||||
|
- definition [ref=f9e68]: 게시 전
|
||||||
|
- generic [ref=f9e69]:
|
||||||
|
- term [ref=f9e70]: 다음
|
||||||
|
- definition [ref=f9e71]:
|
||||||
|
- link "검증하기" [ref=f9e72] [cursor=pointer]:
|
||||||
|
- /url: /studio/documents/f363edc8-2c13-4995-acda-934237034a85/validation
|
||||||
|
- generic [ref=f9e73]:
|
||||||
|
- term [ref=f9e74]: 수정
|
||||||
|
- definition [ref=f9e75]:
|
||||||
|
- time [ref=f9e76]: 2026. 8. 21.
|
||||||
|
- button "삭제" [ref=f9e77]
|
||||||
|
- paragraph [ref=f9e78]
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
- generic [ref=f10e19]:
|
||||||
|
- link "본문으로 건너뛰기" [ref=f10e20] [cursor=pointer]:
|
||||||
|
- /url: "#main-content"
|
||||||
|
- banner [ref=f10e21]:
|
||||||
|
- generic [ref=f10e22]:
|
||||||
|
- link "TechLog Studio" [ref=f10e23] [cursor=pointer]:
|
||||||
|
- /url: /studio
|
||||||
|
- text: TechLog
|
||||||
|
- generic [ref=f10e24]: Studio
|
||||||
|
- navigation "Studio 주 탐색" [ref=f10e26]:
|
||||||
|
- link "작업본" [ref=f10e27] [cursor=pointer]:
|
||||||
|
- /url: /studio/documents
|
||||||
|
- link "게시 기록" [ref=f10e28] [cursor=pointer]:
|
||||||
|
- /url: /studio/publications
|
||||||
|
- link "새 문서" [ref=f10e29] [cursor=pointer]:
|
||||||
|
- /url: /studio/documents/new
|
||||||
|
- link "주제·프로젝트" [ref=f10e30] [cursor=pointer]:
|
||||||
|
- /url: /studio/taxonomy
|
||||||
|
- link "릴리즈" [ref=f10e31] [cursor=pointer]:
|
||||||
|
- /url: /studio/releases
|
||||||
|
- link "공개 사이트 보기" [ref=f10e32] [cursor=pointer]:
|
||||||
|
- /url: /
|
||||||
|
- button "로그아웃" [ref=f10e33]
|
||||||
|
- main [ref=f10e34]:
|
||||||
|
- generic [ref=f10e1]:
|
||||||
|
- generic [ref=f10e2]:
|
||||||
|
- paragraph [ref=f10e3]: ASSET LIBRARY
|
||||||
|
- heading "Asset" [level=1] [ref=f10e4]
|
||||||
|
- paragraph [ref=f10e5]: 업로드한 Asset을 검색하고 사용처를 확인하며, 사용하지 않는 Asset을 정리합니다.
|
||||||
|
- region "Asset 검색 도구" [ref=f10e6]:
|
||||||
|
- search [ref=f10e7]:
|
||||||
|
- generic [ref=f10e8]: Asset 검색
|
||||||
|
- generic [ref=f10e9]:
|
||||||
|
- searchbox "Asset 검색" [ref=f10e10]
|
||||||
|
- button "검색" [ref=f10e11]
|
||||||
|
- status
|
||||||
|
- status [ref=f10e12]: 1개의 Asset
|
||||||
|
- list [ref=f10e13]:
|
||||||
|
- listitem [ref=f10e14]:
|
||||||
|
- button "screenshot-from-2026-08-21-18-04-49-72f1f9c6" [ref=f10e15]
|
||||||
|
- text: READY
|
||||||
|
- generic [ref=f10e16]: 사용 0건
|
||||||
|
- generic "screenshot-from-2026-08-21-18-04-49-72f1f9c6 상세" [ref=f10e35]:
|
||||||
|
- heading "screenshot-from-2026-08-21-18-04-49-72f1f9c6" [active] [level=2] [ref=f10e36]
|
||||||
|
- paragraph [ref=f10e37]: READY
|
||||||
|
- paragraph [ref=f10e38]: 사용 중인 문서가 없습니다.
|
||||||
|
- generic [ref=f10e39]:
|
||||||
|
- button "삭제" [ref=f10e40]
|
||||||
|
- button "닫기" [ref=f10e41]
|
||||||
|
- paragraph [ref=f10e42]
|
||||||
@@ -38,28 +38,28 @@
|
|||||||
},
|
},
|
||||||
"contractSet": {
|
"contractSet": {
|
||||||
"setAlgorithm": "CA_CONTRACT_SET_V1",
|
"setAlgorithm": "CA_CONTRACT_SET_V1",
|
||||||
"setDigest": "sha256:38329cfe645e1d6cbfc7a9bb3e20e23b579a286ad0d1254982c70de5c6055ae8",
|
"setDigest": "sha256:8b5bcbc235bc825483002374f84d433e2668a613e270e6558934fa277bd62762",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"packageId": "@tech-log/management-contract",
|
"packageId": "@tech-log/management-contract",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"digest": "sha256:d19ae7c4fbcac924a356bbb0cc1a46a4046ecec701158ca0a9b7cc089bbaf878",
|
"digest": "sha256:d19ae7c4fbcac924a356bbb0cc1a46a4046ecec701158ca0a9b7cc089bbaf878",
|
||||||
"runtimeProtocolVersion": 1,
|
"runtimeProtocolVersion": 1,
|
||||||
"sourceRevision": "65a04fc"
|
"sourceRevision": "b195b29"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"packageId": "@tech-log/public-contract",
|
"packageId": "@tech-log/public-contract",
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"digest": "sha256:6575a09317a1ffe951747b12102ad2cf884110007426a45d6f59a53b65612d59",
|
"digest": "sha256:6575a09317a1ffe951747b12102ad2cf884110007426a45d6f59a53b65612d59",
|
||||||
"runtimeProtocolVersion": 1,
|
"runtimeProtocolVersion": 1,
|
||||||
"sourceRevision": "65a04fc"
|
"sourceRevision": "b195b29"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"packageId": "@tech-log/studio-contract",
|
"packageId": "@tech-log/studio-contract",
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"digest": "sha256:5229865c3d242f19d75030d3f524a44dfebbf444324068d6ae88e43b8047dba4",
|
"digest": "sha256:674327a82951fd4a1bc2594c858072dfcd0b9abe7c63198283da5d8c92a04326",
|
||||||
"runtimeProtocolVersion": 1,
|
"runtimeProtocolVersion": 1,
|
||||||
"sourceRevision": "65a04fc"
|
"sourceRevision": "b195b29"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import type {
|
|||||||
TopicEdit,
|
TopicEdit,
|
||||||
} from "../../contracts/management/contract.ts";
|
} from "../../contracts/management/contract.ts";
|
||||||
import type { ManagementGateway } from "../../application/ports/management-gateway.ts";
|
import type { ManagementGateway } from "../../application/ports/management-gateway.ts";
|
||||||
|
import { ManagementGatewayError } from "../../application/ports/management-gateway-error.ts";
|
||||||
import type { StudioOperationExecutor } from "./http-studio-gateway.ts";
|
import type { StudioOperationExecutor } from "./http-studio-gateway.ts";
|
||||||
|
|
||||||
export type { ManagementGateway };
|
export type { ManagementGateway };
|
||||||
@@ -23,17 +24,10 @@ const ROUTE_ID = "TECH_LOG_STUDIO";
|
|||||||
* Studio 쪽 상태 처리를 그대로 쓴다. 여기서 Result 로 감싸면 이 표면만 다른 규약이 된다.
|
* Studio 쪽 상태 처리를 그대로 쓴다. 여기서 Result 로 감싸면 이 표면만 다른 규약이 된다.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export class ManagementGatewayError extends Error {
|
export {
|
||||||
readonly operationId: string;
|
ManagementGatewayError,
|
||||||
readonly code: string;
|
managementFailureMessage,
|
||||||
|
} from "../../application/ports/management-gateway-error.ts";
|
||||||
constructor(operationId: string, code: string) {
|
|
||||||
super(`${operationId}: ${code}`);
|
|
||||||
this.name = "ManagementGatewayError";
|
|
||||||
this.operationId = operationId;
|
|
||||||
this.code = code;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createHttpManagementGateway(
|
export function createHttpManagementGateway(
|
||||||
deps: Readonly<{ operations: StudioOperationExecutor }>,
|
deps: Readonly<{ operations: StudioOperationExecutor }>,
|
||||||
@@ -46,7 +40,11 @@ export function createHttpManagementGateway(
|
|||||||
// the code under `error` — reading `problem.code` found nothing and every
|
// the code under `error` — reading `problem.code` found nothing and every
|
||||||
// failure surfaced as the literal "PROBLEM", matching no i18n key.
|
// failure surfaced as the literal "PROBLEM", matching no i18n key.
|
||||||
const body = outcome.problem as
|
const body = outcome.problem as
|
||||||
| Readonly<{ code?: unknown; error?: Readonly<{ code?: unknown }> }>
|
| Readonly<{
|
||||||
|
code?: unknown;
|
||||||
|
detail?: unknown;
|
||||||
|
error?: Readonly<{ code?: unknown; message?: unknown }>;
|
||||||
|
}>
|
||||||
| null;
|
| null;
|
||||||
const code =
|
const code =
|
||||||
typeof body?.code === "string"
|
typeof body?.code === "string"
|
||||||
@@ -54,9 +52,15 @@ export function createHttpManagementGateway(
|
|||||||
: typeof body?.error?.code === "string"
|
: typeof body?.error?.code === "string"
|
||||||
? body.error.code
|
? body.error.code
|
||||||
: "PROBLEM";
|
: "PROBLEM";
|
||||||
throw new ManagementGatewayError(operationId, code);
|
const detail =
|
||||||
|
typeof body?.error?.message === "string"
|
||||||
|
? body.error.message
|
||||||
|
: typeof body?.detail === "string"
|
||||||
|
? body.detail
|
||||||
|
: "";
|
||||||
|
throw new ManagementGatewayError(operationId, code, detail);
|
||||||
}
|
}
|
||||||
throw new ManagementGatewayError(operationId, outcome.kind);
|
throw new ManagementGatewayError(operationId, outcome.kind, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
return Object.freeze({
|
return Object.freeze({
|
||||||
|
|||||||
@@ -170,16 +170,16 @@ export function validateWorkingCopy(document: WorkingCopy, dependencies: Validat
|
|||||||
const has = (id: string | null, type: CatalogEntry["type"]) => Boolean(id && dependencies.catalog.some((entry) => entry.id === id && entry.type === type));
|
const has = (id: string | null, type: CatalogEntry["type"]) => Boolean(id && dependencies.catalog.some((entry) => entry.id === id && entry.type === type));
|
||||||
if (blank(document.title)) error("TITLE_REQUIRED", "/title", "제목을 입력하세요.");
|
if (blank(document.title)) error("TITLE_REQUIRED", "/title", "제목을 입력하세요.");
|
||||||
if (blank(document.slug)) error("SLUG_REQUIRED", "/slug", "slug를 입력하세요."); else if (dependencies.documents.some((item) => item.id !== document.id && item.slug === document.slug)) error("SLUG_DUPLICATE", "/slug", "중복 slug입니다.");
|
if (blank(document.slug)) error("SLUG_REQUIRED", "/slug", "slug를 입력하세요."); else if (dependencies.documents.some((item) => item.id !== document.id && item.slug === document.slug)) error("SLUG_DUPLICATE", "/slug", "중복 slug입니다.");
|
||||||
if (blank(document.summary)) error("SUMMARY_REQUIRED", "/summary", "요약을 입력하세요.");
|
if (blank(document.summary)) warning("SUMMARY_REQUIRED", "/summary", "요약을 입력하세요.");
|
||||||
if (!has(document.topicId, "TOPIC")) error("TOPIC_REQUIRED", "/topicId", "Topic을 선택하세요.");
|
if (!has(document.topicId, "TOPIC")) warning("TOPIC_REQUIRED", "/topicId", "Topic을 선택하세요.");
|
||||||
if (!document.projectId) {
|
if (!document.projectId) {
|
||||||
if (document.kind === "PROJECT_DECISION") error("DECISION_PROJECT_REQUIRED", "/projectId", "Decision에는 Project가 필요합니다.");
|
if (document.kind === "PROJECT_DECISION") warning("DECISION_PROJECT_REQUIRED", "/projectId", "Decision에는 Project가 필요합니다.");
|
||||||
else warning("PROJECT_MISSING", "/projectId", "Project 연결을 권장합니다.");
|
else warning("PROJECT_MISSING", "/projectId", "Project 연결을 권장합니다.");
|
||||||
} else if (!has(document.projectId, "PROJECT")) error("PROJECT_NOT_FOUND", "/projectId", "Project를 찾을 수 없습니다.");
|
} else if (!has(document.projectId, "PROJECT")) error("PROJECT_NOT_FOUND", "/projectId", "Project를 찾을 수 없습니다.");
|
||||||
document.relations.forEach((relation, index) => { if (!has(relation.targetId, "RELATION")) error("RELATION_TARGET_NOT_FOUND", `/relations/${index}/targetId`, "관계 대상을 찾을 수 없습니다."); });
|
document.relations.forEach((relation, index) => { if (!has(relation.targetId, "RELATION")) error("RELATION_TARGET_NOT_FOUND", `/relations/${index}/targetId`, "관계 대상을 찾을 수 없습니다."); });
|
||||||
if (document.kind === "CASE") {
|
if (document.kind === "CASE") {
|
||||||
if (blank(document.problem)) error("CASE_PROBLEM_REQUIRED", "/problem", "문제를 입력하세요."); if (blank(document.conclusion)) error("CASE_CONCLUSION_REQUIRED", "/conclusion", "결론을 입력하세요.");
|
if (blank(document.problem)) warning("CASE_PROBLEM_REQUIRED", "/problem", "문제를 입력하세요."); if (blank(document.conclusion)) warning("CASE_CONCLUSION_REQUIRED", "/conclusion", "결론을 입력하세요.");
|
||||||
if (blank(document.bodyMarkdown)) error("CASE_BODY_REQUIRED", "/bodyMarkdown", "본문을 입력하세요.");
|
if (blank(document.bodyMarkdown)) warning("CASE_BODY_REQUIRED", "/bodyMarkdown", "본문을 입력하세요.");
|
||||||
else try {
|
else try {
|
||||||
const evidenceCatalog = [...dependencies.catalog, ...evidenceCatalogEntriesFromAssets(dependencies.assets)];
|
const evidenceCatalog = [...dependencies.catalog, ...evidenceCatalogEntriesFromAssets(dependencies.assets)];
|
||||||
// The key gate is the preview projection's gate, verbatim: a resolvable
|
// The key gate is the preview projection's gate, verbatim: a resolvable
|
||||||
@@ -202,20 +202,20 @@ export function validateWorkingCopy(document: WorkingCopy, dependencies: Validat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch { error("CONTENT_FORMAT_INVALID", "/bodyMarkdown", "지원하는 문법을 사용하세요."); }
|
} catch { error("CONTENT_FORMAT_INVALID", "/bodyMarkdown", "지원하는 문법을 사용하세요."); }
|
||||||
if (!document.lastVerifiedOn) error("LAST_VERIFIED_ON_REQUIRED", "/lastVerifiedOn", "검증일을 입력하세요."); else if (dependencies.now.getTime() - Date.parse(`${document.lastVerifiedOn}T00:00:00Z`) > 30 * 86_400_000) warning("VERIFICATION_OLDER_THAN_30_DAYS", "/lastVerifiedOn", "30일이 지났습니다.");
|
if (!document.lastVerifiedOn) warning("LAST_VERIFIED_ON_REQUIRED", "/lastVerifiedOn", "검증일을 입력하세요."); else if (dependencies.now.getTime() - Date.parse(`${document.lastVerifiedOn}T00:00:00Z`) > 30 * 86_400_000) warning("VERIFICATION_OLDER_THAN_30_DAYS", "/lastVerifiedOn", "30일이 지났습니다.");
|
||||||
} else if (document.kind === "REFERENCE") {
|
} else if (document.kind === "REFERENCE") {
|
||||||
if (blank(document.purpose)) error("REFERENCE_PURPOSE_REQUIRED", "/purpose", "목적을 입력하세요."); if (!document.rules.length) error("REFERENCE_RULE_REQUIRED", "/rules", "규칙이 필요합니다."); if (!document.applyWhen.length) error("REFERENCE_APPLY_WHEN_REQUIRED", "/applyWhen", "적용 조건이 필요합니다."); if (!document.verifiedOn) error("VERIFIED_ON_REQUIRED", "/verifiedOn", "검증일이 필요합니다."); if (!document.examples.length) warning("REFERENCE_EXAMPLE_MISSING", "/examples", "예시를 권장합니다.");
|
if (blank(document.purpose)) warning("REFERENCE_PURPOSE_REQUIRED", "/purpose", "목적을 입력하세요."); if (!document.rules.length) warning("REFERENCE_RULE_REQUIRED", "/rules", "규칙이 필요합니다."); if (!document.applyWhen.length) warning("REFERENCE_APPLY_WHEN_REQUIRED", "/applyWhen", "적용 조건이 필요합니다."); if (!document.verifiedOn) warning("VERIFIED_ON_REQUIRED", "/verifiedOn", "검증일이 필요합니다."); if (!document.examples.length) warning("REFERENCE_EXAMPLE_MISSING", "/examples", "예시를 권장합니다.");
|
||||||
} else if (document.kind === "QUESTION") {
|
} else if (document.kind === "QUESTION") {
|
||||||
if (!document.questionStatus) error("QUESTION_STATUS_REQUIRED", "/questionStatus", "상태가 필요합니다."); if (blank(document.nextValidation)) error("NEXT_VALIDATION_REQUIRED", "/nextValidation", "다음 검증이 필요합니다."); if (!document.facts.length) error("QUESTION_FACT_REQUIRED", "/facts", "사실이 필요합니다.");
|
if (!document.questionStatus) warning("QUESTION_STATUS_REQUIRED", "/questionStatus", "상태가 필요합니다."); if (blank(document.nextValidation)) warning("NEXT_VALIDATION_REQUIRED", "/nextValidation", "다음 검증이 필요합니다."); if (!document.facts.length) warning("QUESTION_FACT_REQUIRED", "/facts", "사실이 필요합니다.");
|
||||||
if (document.questionStatus === "OPEN") { if (!document.unknowns.length) error("QUESTION_UNKNOWN_REQUIRED", "/unknowns", "미확인 사항이 필요합니다."); if (document.resolution) error("OPEN_QUESTION_RESOLUTION_FORBIDDEN", "/resolution", "열린 질문에는 결론을 둘 수 없습니다."); }
|
if (document.questionStatus === "OPEN") { if (!document.unknowns.length) error("QUESTION_UNKNOWN_REQUIRED", "/unknowns", "미확인 사항이 필요합니다."); if (document.resolution) error("OPEN_QUESTION_RESOLUTION_FORBIDDEN", "/resolution", "열린 질문에는 결론을 둘 수 없습니다."); }
|
||||||
if (document.questionStatus === "RESOLVED") { if (!document.resolution) error("QUESTION_RESOLUTION_REQUIRED", "/resolution", "해결 내용이 필요합니다."); else { if (blank(document.resolution.summary)) error("RESOLUTION_SUMMARY_REQUIRED", "/resolution/summary", "요약이 필요합니다."); if (!has(document.resolution.evidenceTargetId, "EVIDENCE")) error("RESOLUTION_EVIDENCE_REQUIRED", "/resolution/evidenceTargetId", "근거가 필요합니다."); if (blank(document.resolution.linkLabel)) error("RESOLUTION_LINK_LABEL_REQUIRED", "/resolution/linkLabel", "링크 문구가 필요합니다."); } }
|
if (document.questionStatus === "RESOLVED") { if (!document.resolution) warning("QUESTION_RESOLUTION_REQUIRED", "/resolution", "해결 내용이 필요합니다."); else { if (blank(document.resolution.summary)) warning("RESOLUTION_SUMMARY_REQUIRED", "/resolution/summary", "요약이 필요합니다."); if (!has(document.resolution.evidenceTargetId, "EVIDENCE")) error("RESOLUTION_EVIDENCE_REQUIRED", "/resolution/evidenceTargetId", "근거가 필요합니다."); if (blank(document.resolution.linkLabel)) warning("RESOLUTION_LINK_LABEL_REQUIRED", "/resolution/linkLabel", "링크 문구가 필요합니다."); } }
|
||||||
if (document.options.length < 2) warning("QUESTION_OPTIONS_FEWER_THAN_TWO", "/options", "선택지 두 개를 권장합니다.");
|
if (document.options.length < 2) warning("QUESTION_OPTIONS_FEWER_THAN_TWO", "/options", "선택지 두 개를 권장합니다.");
|
||||||
} else {
|
} else {
|
||||||
if (!document.decisionStatus) error("DECISION_STATUS_REQUIRED", "/decisionStatus", "결정 상태가 필요합니다.");
|
if (!document.decisionStatus) warning("DECISION_STATUS_REQUIRED", "/decisionStatus", "결정 상태가 필요합니다.");
|
||||||
if (!document.decidedOn) error("DECIDED_ON_REQUIRED", "/decidedOn", "결정일이 필요합니다.");
|
if (!document.decidedOn) warning("DECIDED_ON_REQUIRED", "/decidedOn", "결정일이 필요합니다.");
|
||||||
if (blank(document.statement)) error("DECISION_STATEMENT_REQUIRED", "/statement", "결정문이 필요합니다.");
|
if (blank(document.statement)) warning("DECISION_STATEMENT_REQUIRED", "/statement", "결정문이 필요합니다.");
|
||||||
if (blank(document.rationale)) error("DECISION_RATIONALE_REQUIRED", "/rationale", "판단 이유가 필요합니다.");
|
if (blank(document.rationale)) warning("DECISION_RATIONALE_REQUIRED", "/rationale", "판단 이유가 필요합니다.");
|
||||||
if (!document.consequences.length) error("DECISION_CONSEQUENCE_REQUIRED", "/consequences", "영향이 하나 이상 필요합니다.");
|
if (!document.consequences.length) warning("DECISION_CONSEQUENCE_REQUIRED", "/consequences", "영향이 하나 이상 필요합니다.");
|
||||||
if (!document.relations.length) error("DECISION_EVIDENCE_REQUIRED", "/relations", "근거 기록이 하나 이상 필요합니다.");
|
if (!document.relations.length) error("DECISION_EVIDENCE_REQUIRED", "/relations", "근거 기록이 하나 이상 필요합니다.");
|
||||||
}
|
}
|
||||||
const validatedAt = dependencies.now.toISOString();
|
const validatedAt = dependencies.now.toISOString();
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* 관리 표면의 실패.
|
||||||
|
*
|
||||||
|
* <p>어댑터가 아니라 포트 계층에 두는 이유는 화면이 이것을 읽어야 하기 때문이다 — `presentation`
|
||||||
|
* 은 `adapters` 를 보지 않는다 (`feature-presentation-does-not-know-outbound-adapters`).
|
||||||
|
* Studio 쪽 `studio-gateway-error.ts` 가 같은 이유로 같은 자리에 있다.
|
||||||
|
*/
|
||||||
|
export class ManagementGatewayError extends Error {
|
||||||
|
readonly operationId: string;
|
||||||
|
readonly code: string;
|
||||||
|
/**
|
||||||
|
* 서버가 준, 사람이 읽을 수 있는 이유.
|
||||||
|
*
|
||||||
|
* <p>이것이 없어서 화면은 실패할 때마다 자기가 지어낸 문구를 보여 줬다 — "게시 중이거나,
|
||||||
|
* 참조하는 곳이 있거나, 다른 곳에서 먼저 수정되었을 수 있습니다" 같은 추측 셋. 서버는 정확히
|
||||||
|
* 무엇인지 알고 그것을 보내 주는데도 그랬고, 그래서 버전 충돌도 "사용 중" 으로 읽혔다.
|
||||||
|
*/
|
||||||
|
readonly detail: string;
|
||||||
|
|
||||||
|
constructor(operationId: string, code: string, detail: string) {
|
||||||
|
super(`${operationId}: ${code}`);
|
||||||
|
this.name = "ManagementGatewayError";
|
||||||
|
this.operationId = operationId;
|
||||||
|
this.code = code;
|
||||||
|
this.detail = detail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 실패에서 서버가 준 문구를 꺼낸다. 없으면 부른 쪽이 준 기본값을 쓴다. */
|
||||||
|
export function managementFailureMessage(error: unknown, fallback: string): string {
|
||||||
|
return error instanceof ManagementGatewayError && error.detail ? error.detail : fallback;
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
"packageId": "@tech-log/management-contract",
|
"packageId": "@tech-log/management-contract",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"digest": "sha256:d19ae7c4fbcac924a356bbb0cc1a46a4046ecec701158ca0a9b7cc089bbaf878",
|
"digest": "sha256:d19ae7c4fbcac924a356bbb0cc1a46a4046ecec701158ca0a9b7cc089bbaf878",
|
||||||
"sourceRevision": "65a04fc",
|
"sourceRevision": "b195b29",
|
||||||
"operationIds": [
|
"operationIds": [
|
||||||
"createCaseDraft",
|
"createCaseDraft",
|
||||||
"getCaseForEdit",
|
"getCaseForEdit",
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"packageId": "@tech-log/public-contract",
|
"packageId": "@tech-log/public-contract",
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"digest": "sha256:6575a09317a1ffe951747b12102ad2cf884110007426a45d6f59a53b65612d59",
|
"digest": "sha256:6575a09317a1ffe951747b12102ad2cf884110007426a45d6f59a53b65612d59",
|
||||||
"sourceRevision": "65a04fc",
|
"sourceRevision": "b195b29",
|
||||||
"operationIds": [
|
"operationIds": [
|
||||||
"getPublicSite",
|
"getPublicSite",
|
||||||
"getPublicHome",
|
"getPublicHome",
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"packageId": "@tech-log/studio-contract",
|
"packageId": "@tech-log/studio-contract",
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"digest": "sha256:5229865c3d242f19d75030d3f524a44dfebbf444324068d6ae88e43b8047dba4",
|
"digest": "sha256:674327a82951fd4a1bc2594c858072dfcd0b9abe7c63198283da5d8c92a04326",
|
||||||
"sourceRevision": "65a04fc",
|
"sourceRevision": "b195b29",
|
||||||
"operationIds": [
|
"operationIds": [
|
||||||
"getStudioSession",
|
"getStudioSession",
|
||||||
"getStudioDashboard",
|
"getStudioDashboard",
|
||||||
|
|||||||
@@ -871,7 +871,7 @@ components:
|
|||||||
required: [id, text, order]
|
required: [id, text, order]
|
||||||
properties:
|
properties:
|
||||||
id: { type: string, format: uuid }
|
id: { type: string, format: uuid }
|
||||||
text: { type: string, minLength: 1, maxLength: 100000 }
|
text: { type: string, maxLength: 100000 }
|
||||||
order: { type: integer, minimum: 0 }
|
order: { type: integer, minimum: 0 }
|
||||||
ReferenceRule:
|
ReferenceRule:
|
||||||
type: object
|
type: object
|
||||||
@@ -1242,7 +1242,7 @@ components:
|
|||||||
kind: { $ref: "#/components/schemas/RecordKind" }
|
kind: { $ref: "#/components/schemas/RecordKind" }
|
||||||
slug: { type: string, minLength: 3, maxLength: 100, pattern: "^[a-z0-9]+(?:-[a-z0-9]+)*$" }
|
slug: { type: string, minLength: 3, maxLength: 100, pattern: "^[a-z0-9]+(?:-[a-z0-9]+)*$" }
|
||||||
title: { type: string, minLength: 1, maxLength: 120 }
|
title: { type: string, minLength: 1, maxLength: 120 }
|
||||||
summary: { type: string, minLength: 1, maxLength: 300 }
|
summary: { type: string, maxLength: 300 }
|
||||||
publicPath: { type: string, minLength: 1, maxLength: 500 }
|
publicPath: { type: string, minLength: 1, maxLength: 500 }
|
||||||
topic: { $ref: "#/components/schemas/DisplayTarget" }
|
topic: { $ref: "#/components/schemas/DisplayTarget" }
|
||||||
project:
|
project:
|
||||||
@@ -1257,7 +1257,7 @@ components:
|
|||||||
required: [type, text]
|
required: [type, text]
|
||||||
properties:
|
properties:
|
||||||
type: { type: string, enum: [TEXT] }
|
type: { type: string, enum: [TEXT] }
|
||||||
text: { type: string, minLength: 1, maxLength: 100000 }
|
text: { type: string, maxLength: 100000 }
|
||||||
InlineContainer:
|
InlineContainer:
|
||||||
type: object
|
type: object
|
||||||
required: [type, children]
|
required: [type, children]
|
||||||
@@ -1483,8 +1483,8 @@ components:
|
|||||||
required: [kind, problem, conclusion, environment, reproduction, lastVerifiedOn, bodyBlocks]
|
required: [kind, problem, conclusion, environment, reproduction, lastVerifiedOn, bodyBlocks]
|
||||||
properties:
|
properties:
|
||||||
kind: { type: string, enum: [CASE] }
|
kind: { type: string, enum: [CASE] }
|
||||||
problem: { type: string, minLength: 1, maxLength: 100000 }
|
problem: { type: string, maxLength: 100000 }
|
||||||
conclusion: { type: string, minLength: 1, maxLength: 100000 }
|
conclusion: { type: string, maxLength: 100000 }
|
||||||
environment: { type: string, maxLength: 100000 }
|
environment: { type: string, maxLength: 100000 }
|
||||||
reproduction: { type: string, maxLength: 100000 }
|
reproduction: { type: string, maxLength: 100000 }
|
||||||
lastVerifiedOn: { type: string, format: date }
|
lastVerifiedOn: { type: string, format: date }
|
||||||
@@ -1497,7 +1497,7 @@ components:
|
|||||||
required: [kind, purpose, rules, applyWhen, exceptions, examples, verifiedOn]
|
required: [kind, purpose, rules, applyWhen, exceptions, examples, verifiedOn]
|
||||||
properties:
|
properties:
|
||||||
kind: { type: string, enum: [REFERENCE] }
|
kind: { type: string, enum: [REFERENCE] }
|
||||||
purpose: { type: string, minLength: 1, maxLength: 100000 }
|
purpose: { type: string, maxLength: 100000 }
|
||||||
rules: { type: array, minItems: 1, maxItems: 50, items: { $ref: "#/components/schemas/ReferenceRule" } }
|
rules: { type: array, minItems: 1, maxItems: 50, items: { $ref: "#/components/schemas/ReferenceRule" } }
|
||||||
applyWhen: { type: array, minItems: 1, maxItems: 50, items: { $ref: "#/components/schemas/OrderedText" } }
|
applyWhen: { type: array, minItems: 1, maxItems: 50, items: { $ref: "#/components/schemas/OrderedText" } }
|
||||||
exceptions: { type: array, maxItems: 50, items: { $ref: "#/components/schemas/OrderedText" } }
|
exceptions: { type: array, maxItems: 50, items: { $ref: "#/components/schemas/OrderedText" } }
|
||||||
@@ -1508,7 +1508,7 @@ components:
|
|||||||
additionalProperties: false
|
additionalProperties: false
|
||||||
required: [summary, evidenceTarget, linkLabel]
|
required: [summary, evidenceTarget, linkLabel]
|
||||||
properties:
|
properties:
|
||||||
summary: { type: string, minLength: 1, maxLength: 100000 }
|
summary: { type: string, maxLength: 100000 }
|
||||||
evidenceTarget: { $ref: "#/components/schemas/DisplayTarget" }
|
evidenceTarget: { $ref: "#/components/schemas/DisplayTarget" }
|
||||||
linkLabel: { type: string, minLength: 1, maxLength: 120 }
|
linkLabel: { type: string, minLength: 1, maxLength: 120 }
|
||||||
QuestionPublicRenderModel:
|
QuestionPublicRenderModel:
|
||||||
@@ -1528,7 +1528,7 @@ components:
|
|||||||
unknowns: { type: array, maxItems: 50, items: { $ref: "#/components/schemas/OrderedText" } }
|
unknowns: { type: array, maxItems: 50, items: { $ref: "#/components/schemas/OrderedText" } }
|
||||||
constraints: { type: array, maxItems: 50, items: { $ref: "#/components/schemas/OrderedText" } }
|
constraints: { type: array, maxItems: 50, items: { $ref: "#/components/schemas/OrderedText" } }
|
||||||
options: { type: array, maxItems: 50, items: { $ref: "#/components/schemas/QuestionOption" } }
|
options: { type: array, maxItems: 50, items: { $ref: "#/components/schemas/QuestionOption" } }
|
||||||
nextValidation: { type: string, minLength: 1, maxLength: 100000 }
|
nextValidation: { type: string, maxLength: 100000 }
|
||||||
resolution:
|
resolution:
|
||||||
oneOf:
|
oneOf:
|
||||||
- { $ref: "#/components/schemas/ResolvedQuestionResolution" }
|
- { $ref: "#/components/schemas/ResolvedQuestionResolution" }
|
||||||
@@ -1543,8 +1543,8 @@ components:
|
|||||||
kind: { type: string, enum: [PROJECT_DECISION] }
|
kind: { type: string, enum: [PROJECT_DECISION] }
|
||||||
status: { type: string, enum: [PROPOSED, ADOPTED] }
|
status: { type: string, enum: [PROPOSED, ADOPTED] }
|
||||||
decidedOn: { type: string, format: date }
|
decidedOn: { type: string, format: date }
|
||||||
statement: { type: string, minLength: 1, maxLength: 100000 }
|
statement: { type: string, maxLength: 100000 }
|
||||||
rationale: { type: string, minLength: 1, maxLength: 100000 }
|
rationale: { type: string, maxLength: 100000 }
|
||||||
consequences: { type: array, maxItems: 50, items: { $ref: "#/components/schemas/OrderedText" } }
|
consequences: { type: array, maxItems: 50, items: { $ref: "#/components/schemas/OrderedText" } }
|
||||||
PublicRenderModel:
|
PublicRenderModel:
|
||||||
oneOf:
|
oneOf:
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { useEffect, useState, type FormEvent } from "react";
|
|||||||
|
|
||||||
import type { ListDocumentsQuery } from "../../../application/ports/studio-gateway.ts";
|
import type { ListDocumentsQuery } from "../../../application/ports/studio-gateway.ts";
|
||||||
import type { DocumentPage } from "../../../contracts/studio/contract.ts";
|
import type { DocumentPage } from "../../../contracts/studio/contract.ts";
|
||||||
|
import { managementFailureMessage } from "../../../application/ports/management-gateway-error.ts";
|
||||||
import { useStudio } from "../use-studio.ts";
|
import { useStudio } from "../use-studio.ts";
|
||||||
import { GuardedStudioLink } from "./guarded-studio-link.tsx";
|
import { GuardedStudioLink } from "./guarded-studio-link.tsx";
|
||||||
|
|
||||||
@@ -98,10 +99,10 @@ export function DocumentList() {
|
|||||||
? { ...current, items: current.items.filter((row) => row.id !== item.id) }
|
? { ...current, items: current.items.filter((row) => row.id !== item.id) }
|
||||||
: current,
|
: current,
|
||||||
);
|
);
|
||||||
} catch {
|
} catch (error) {
|
||||||
setDeleteError(
|
// 서버는 무엇이 막았는지 정확히 알고 그것을 보내 준다. 예전에는 그 문구를 버리고 추측 셋을
|
||||||
"삭제하지 못했습니다. 게시 중이거나, 이 기록을 참조하는 곳이 있거나, 다른 곳에서 먼저 수정되었을 수 있습니다.",
|
// 늘어놓아, 버전 충돌도 "사용 중" 으로 읽혔다.
|
||||||
);
|
setDeleteError(managementFailureMessage(error, "삭제하지 못했습니다."));
|
||||||
} finally {
|
} finally {
|
||||||
setDeletingId(null);
|
setDeletingId(null);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import type {
|
|||||||
ReleaseIndexItem,
|
ReleaseIndexItem,
|
||||||
ReleaseUpdateRequest,
|
ReleaseUpdateRequest,
|
||||||
} from "../../../contracts/management/contract.ts";
|
} from "../../../contracts/management/contract.ts";
|
||||||
|
import { managementFailureMessage } from "../../../application/ports/management-gateway-error.ts";
|
||||||
import { useStudio } from "../use-studio.ts";
|
import { useStudio } from "../use-studio.ts";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -150,8 +151,8 @@ export function ReleaseManager() {
|
|||||||
setRequestAnnouncement(`릴리즈 ${title} 초안을 만들었습니다.`);
|
setRequestAnnouncement(`릴리즈 ${title} 초안을 만들었습니다.`);
|
||||||
setSelectedId(created.id);
|
setSelectedId(created.id);
|
||||||
reload();
|
reload();
|
||||||
} catch {
|
} catch (error) {
|
||||||
setError("릴리즈를 만들지 못했습니다.");
|
setError(managementFailureMessage(error, "릴리즈를 만들지 못했습니다."));
|
||||||
} finally {
|
} finally {
|
||||||
setPending(false);
|
setPending(false);
|
||||||
}
|
}
|
||||||
@@ -180,8 +181,8 @@ export function ReleaseManager() {
|
|||||||
setDraft(toDraft(saved));
|
setDraft(toDraft(saved));
|
||||||
setRequestAnnouncement(`릴리즈 ${saved.versionLabel} 을(를) 저장했습니다.`);
|
setRequestAnnouncement(`릴리즈 ${saved.versionLabel} 을(를) 저장했습니다.`);
|
||||||
reload();
|
reload();
|
||||||
} catch {
|
} catch (error) {
|
||||||
setError("저장하지 못했습니다. 같은 버전이 이미 있거나 다른 곳에서 먼저 수정되었을 수 있습니다.");
|
setError(managementFailureMessage(error, "저장하지 못했습니다."));
|
||||||
} finally {
|
} finally {
|
||||||
setPending(false);
|
setPending(false);
|
||||||
}
|
}
|
||||||
@@ -214,8 +215,8 @@ export function ReleaseManager() {
|
|||||||
await managementGateway.archiveRelease(selectedId, draft.expectedVersion);
|
await managementGateway.archiveRelease(selectedId, draft.expectedVersion);
|
||||||
setRequestAnnouncement("릴리즈를 공개에서 내렸습니다.");
|
setRequestAnnouncement("릴리즈를 공개에서 내렸습니다.");
|
||||||
reload();
|
reload();
|
||||||
} catch {
|
} catch (error) {
|
||||||
setError("공개에서 내리지 못했습니다.");
|
setError(managementFailureMessage(error, "공개에서 내리지 못했습니다."));
|
||||||
} finally {
|
} finally {
|
||||||
setPending(false);
|
setPending(false);
|
||||||
}
|
}
|
||||||
@@ -230,8 +231,8 @@ export function ReleaseManager() {
|
|||||||
if (selectedId === release.id) setSelectedId(null);
|
if (selectedId === release.id) setSelectedId(null);
|
||||||
setRequestAnnouncement(`릴리즈 ${release.versionLabel} 을(를) 삭제했습니다.`);
|
setRequestAnnouncement(`릴리즈 ${release.versionLabel} 을(를) 삭제했습니다.`);
|
||||||
reload();
|
reload();
|
||||||
} catch {
|
} catch (error) {
|
||||||
setError("삭제하지 못했습니다. 공개된 릴리즈는 삭제 대신 공개에서 내려야 합니다.");
|
setError(managementFailureMessage(error, "삭제하지 못했습니다."));
|
||||||
} finally {
|
} finally {
|
||||||
setPending(false);
|
setPending(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useCallback, useEffect, useState, type FormEvent } from "react";
|
import { useCallback, useEffect, useState, type FormEvent } from "react";
|
||||||
|
|
||||||
import type { ProjectIndexItem, TopicEdit } from "../../../contracts/management/contract.ts";
|
import type { ProjectIndexItem, TopicEdit } from "../../../contracts/management/contract.ts";
|
||||||
|
import { managementFailureMessage } from "../../../application/ports/management-gateway-error.ts";
|
||||||
import { slugFromName } from "./slug-from-name.ts";
|
import { slugFromName } from "./slug-from-name.ts";
|
||||||
import { useStudio } from "../use-studio.ts";
|
import { useStudio } from "../use-studio.ts";
|
||||||
|
|
||||||
@@ -75,8 +76,8 @@ export function TaxonomyManager() {
|
|||||||
setTopicSlug("");
|
setTopicSlug("");
|
||||||
setRequestAnnouncement(`주제 ${name} 을(를) 만들었습니다.`);
|
setRequestAnnouncement(`주제 ${name} 을(를) 만들었습니다.`);
|
||||||
reload();
|
reload();
|
||||||
} catch {
|
} catch (error) {
|
||||||
setError("주제를 만들지 못했습니다. 같은 이름이나 slug 가 이미 있을 수 있습니다.");
|
setError(managementFailureMessage(error, "주제를 만들지 못했습니다."));
|
||||||
} finally {
|
} finally {
|
||||||
setPending(false);
|
setPending(false);
|
||||||
}
|
}
|
||||||
@@ -97,8 +98,8 @@ export function TaxonomyManager() {
|
|||||||
setProjectTitle("");
|
setProjectTitle("");
|
||||||
setRequestAnnouncement(`프로젝트 ${title} 을(를) 만들었습니다.`);
|
setRequestAnnouncement(`프로젝트 ${title} 을(를) 만들었습니다.`);
|
||||||
reload();
|
reload();
|
||||||
} catch {
|
} catch (error) {
|
||||||
setError("프로젝트를 만들지 못했습니다.");
|
setError(managementFailureMessage(error, "프로젝트를 만들지 못했습니다."));
|
||||||
} finally {
|
} finally {
|
||||||
setPending(false);
|
setPending(false);
|
||||||
}
|
}
|
||||||
@@ -112,8 +113,8 @@ export function TaxonomyManager() {
|
|||||||
await managementGateway.deleteTopic(topic.id, topic.version);
|
await managementGateway.deleteTopic(topic.id, topic.version);
|
||||||
setRequestAnnouncement(`주제 ${topic.name} 을(를) 삭제했습니다.`);
|
setRequestAnnouncement(`주제 ${topic.name} 을(를) 삭제했습니다.`);
|
||||||
reload();
|
reload();
|
||||||
} catch {
|
} catch (error) {
|
||||||
setError("주제를 삭제하지 못했습니다. 이 주제를 쓰는 기록이 있을 수 있습니다.");
|
setError(managementFailureMessage(error, "주제를 삭제하지 못했습니다."));
|
||||||
} finally {
|
} finally {
|
||||||
setPending(false);
|
setPending(false);
|
||||||
}
|
}
|
||||||
@@ -127,8 +128,8 @@ export function TaxonomyManager() {
|
|||||||
await managementGateway.deleteProject(project.id, project.version);
|
await managementGateway.deleteProject(project.id, project.version);
|
||||||
setRequestAnnouncement(`프로젝트 ${project.name} 을(를) 삭제했습니다.`);
|
setRequestAnnouncement(`프로젝트 ${project.name} 을(를) 삭제했습니다.`);
|
||||||
reload();
|
reload();
|
||||||
} catch {
|
} catch (error) {
|
||||||
setError("프로젝트를 삭제하지 못했습니다. 연결된 기록이 있을 수 있습니다.");
|
setError(managementFailureMessage(error, "프로젝트를 삭제하지 못했습니다."));
|
||||||
} finally {
|
} finally {
|
||||||
setPending(false);
|
setPending(false);
|
||||||
}
|
}
|
||||||
@@ -210,7 +211,12 @@ export function TaxonomyManager() {
|
|||||||
<dl>
|
<dl>
|
||||||
<div>
|
<div>
|
||||||
<dt>상태</dt>
|
<dt>상태</dt>
|
||||||
<dd>{topic.status === "ARCHIVED" ? "보관" : "사용 중"}</dd>
|
{/*
|
||||||
|
`ACTIVE` 를 "사용 중" 이라 적었더니, 방금 만들어 아무도 쓰지 않는 주제까지
|
||||||
|
"사용 중" 으로 보였다. 그 상태에서 삭제가 거절되면 작성자는 둘을 같은
|
||||||
|
말로 읽는다 — 실제로는 서로 다른 이야기다.
|
||||||
|
*/}
|
||||||
|
<dd>{topic.status === "ARCHIVED" ? "보관" : "활성"}</dd>
|
||||||
</div>
|
</div>
|
||||||
</dl>
|
</dl>
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -397,7 +397,10 @@ test("validation issue order, evidence, preview freshness and warning-set public
|
|||||||
{ expectedVersion: 2 },
|
{ expectedVersion: 2 },
|
||||||
{ idempotencyKey: "invalid" },
|
{ idempotencyKey: "invalid" },
|
||||||
);
|
);
|
||||||
assert.equal(invalid.status, "INVALID");
|
// 비어 있음은 더 이상 게시를 막지 않는다 — 무엇을 얼마나 쓸지는 작성자가 정한다. 그래서 이
|
||||||
|
// 문서는 INVALID 가 아니라 WARNINGS 다. 항목이 사라지는 것이 아니라 심각도만 내려간다는 것이
|
||||||
|
// 여기서 확인해야 할 점이고, 그래서 코드 목록은 그대로 둔다.
|
||||||
|
assert.equal(invalid.status, "WARNINGS");
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
invalid.issues.map(({ code }) => code),
|
invalid.issues.map(({ code }) => code),
|
||||||
["QUESTION_FACT_REQUIRED", "QUESTION_OPTIONS_FEWER_THAN_TWO"],
|
["QUESTION_FACT_REQUIRED", "QUESTION_OPTIONS_FEWER_THAN_TWO"],
|
||||||
|
|||||||
Reference in New Issue
Block a user