Kanban 인터프리터는 위키 텍스트를 단일 원본으로 사용한다.
보기 화면에서는 보드 UI로 편집하고, 저장 시 Kanban 블록을 다시 위키 텍스트로 직렬화한다.
=== Info ==== 작업 제목 ==== #cardid123 ===== Description 카드에 대한 설명을 여러 줄로 작성할 수 있다. 새 댓글이 추가되어도 밀리지 않는다. ===== Property * DueDate: [2027-01-01] * Assignee: [User:Aha00a] * Attachment: [[Attachment(path/to/file.png)]] ===== Activity * [User:Aha00a] [2026-05-04]T10:36:17 * ["#cardid123" #작업 제목] moved - '''Done''' to '''Info''' === ToDo ==== 점심 설거지 ==== #cmq4akx08r020y2 ===== Property * Creator: [User:Aha00a] * dateCreated: [2026-06-07]T21:26:27Z * ParentCard: [#cmq4ak09d89cnyp 점심에 할일] ===== Activity * [User:Aha00a] [2026-06-07]T21:29:15Z * ["#cmq4akx08r020y2" #점심 설거지] updated - ParentCard - [#cmq4ak09d89cnyp 점심에 할일] * [User:Aha00a] [2026-06-07]T21:26:27Z * ["#cmq4akx08r020y2" #점심 설거지] added === Pending ==== 점심 식사 ==== #cmq4aksurw4h9b6 ===== Property * Creator: [User:Aha00a] * dateCreated: [2026-06-07]T21:26:22Z * ParentCard: [#cmq4ak09d89cnyp 점심에 할일] ===== Activity * [User:Aha00a] [2026-06-07]T21:30:17Z * ["#cmq4aksurw4h9b6" #점심 식사] moved - '''ToDo''' to '''Pending''' * [User:Aha00a] [2026-06-07]T21:29:10Z * ["#cmq4aksurw4h9b6" #점심 식사] updated - ParentCard - [#cmq4ak09d89cnyp 점심에 할일] * [User:Aha00a] [2026-06-07]T21:26:22Z * ["#cmq4aksurw4h9b6" #점심 식사] added === Doing ==== 점심에 할일 ==== #cmq4ak09d89cnyp ===== Property * Creator: [User:Aha00a] * dateCreated: [2026-06-07]T21:25:45Z * BlockedBy: [#cmq4aiz5lnvvyen 일어나기] ===== Activity * [User:Aha00a] [2026-06-07]T21:29:36Z * ["#cmq4ak09d89cnyp" #점심에 할일] moved - '''ToDo''' to '''Doing''' * [User:Aha00a] [2026-06-07]T21:29:28Z * ["#cmq4ak09d89cnyp" #점심에 할일] updated - BlockedBy - [#cmq4aiz5lnvvyen 일어나기] * [User:Aha00a] [2026-06-07]T21:25:54Z * ["#cmq4ak09d89cnyp" #점심에 할일] renamed - '''점심에할일''' * [User:Aha00a] [2026-06-07]T21:25:45Z * ["#cmq4ak09d89cnyp" #점심에할일] added ==== 점심밥 차리기 ==== #cmq4akmkpy6chrx ===== Property * Creator: [User:Aha00a] * dateCreated: [2026-06-07]T21:26:14Z * ParentCard: [#cmq4ak09d89cnyp 점심에 할일] ===== Activity * [User:Aha00a] [2026-06-07]T21:29:57Z * ["#cmq4akmkpy6chrx" #점심밥 차리기] moved - '''ToDo''' to '''Doing''' * [User:Aha00a] [2026-06-07]T21:29:04Z * ["#cmq4akmkpy6chrx" #점심밥 차리기] updated - ParentCard - [#cmq4ak09d89cnyp 점심에 할일] * [User:Aha00a] [2026-06-07]T21:26:14Z * ["#cmq4akmkpy6chrx" #점심밥 차리기] added === Done ==== 일어나기 ==== #cmq4aiz5lnvvyen ===== Property * Creator: [User:Aha00a] * dateCreated: [2026-06-07]T21:24:57Z ===== Activity * [User:Aha00a] [2026-06-07]T21:28:07Z * ["#cmq4aiz5lnvvyen" #일어나기] moved - '''ToDo''' to '''Done''' * [User:Aha00a] [2026-06-07]T21:24:57Z * ["#cmq4aiz5lnvvyen" #일어나기] added ==== 아침에 할일 ==== #cmq4ajpa7tig43b ===== Property * Creator: [User:Aha00a] * dateCreated: [2026-06-07]T21:25:30Z * BlockedBy: [#cmq4aiz5lnvvyen 일어나기] ===== Activity * [User:Aha00a] [2026-06-07]T21:28:38Z * ["#cmq4ajpa7tig43b" #아침에 할일] moved - '''Doing''' to '''Done''' * [User:Aha00a] [2026-06-07]T21:28:15Z * ["#cmq4ajpa7tig43b" #아침에 할일] updated - BlockedBy - [#cmq4aiz5lnvvyen 일어나기] * [User:Aha00a] [2026-06-07]T21:27:31Z * ["#cmq4ajpa7tig43b" #아침에 할일] moved - '''ToDo''' to '''Doing''' * [User:Aha00a] [2026-06-07]T21:25:50Z * ["#cmq4ajpa7tig43b" #아침에 할일] renamed - '''아침에할일''' * [User:Aha00a] [2026-06-07]T21:25:30Z * ["#cmq4ajpa7tig43b" #아침에할일] added ==== 아침밥 차리기 ==== #cmq4aj3iyvga6ec ===== Property * Creator: [User:Aha00a] * dateCreated: [2026-06-07]T21:25:02Z * ParentCard: [#cmq4ajpa7tig43b 아침에 할일] ===== Activity * [User:Aha00a] [2026-06-07]T21:28:43Z * ["#cmq4aj3iyvga6ec" #아침밥 차리기] moved - '''Doing''' to '''Done''' * [User:Aha00a] [2026-06-07]T21:27:39Z * ["#cmq4aj3iyvga6ec" #아침밥 차리기] moved - '''ToDo''' to '''Doing''' * [User:Aha00a] [2026-06-07]T21:26:56Z * ["#cmq4aj3iyvga6ec" #아침밥 차리기] updated - ParentCard - [#cmq4ajpa7tig43b 아침에 할일] * [User:Aha00a] [2026-06-07]T21:26:34Z * ["#cmq4aj3iyvga6ec" #아침밥 차리기] renamed - '''아침밥차리기''' * [User:Aha00a] [2026-06-07]T21:25:08Z * ["#cmq4aj3iyvga6ec" #아침밥차리기] renamed - '''밥차리기''' * [User:Aha00a] [2026-06-07]T21:25:02Z * ["#cmq4aj3iyvga6ec" #밥차리기] added ==== 아침 식사 ==== #cmq4ajesg43bp1g ===== Property * Creator: [User:Aha00a] * dateCreated: [2026-06-07]T21:25:17Z * ParentCard: [#cmq4ajpa7tig43b 아침에 할일] ===== Activity * [User:Aha00a] [2026-06-07]T21:28:45Z * ["#cmq4ajesg43bp1g" #아침 식사] moved - '''Doing''' to '''Done''' * [User:Aha00a] [2026-06-07]T21:27:42Z * ["#cmq4ajesg43bp1g" #아침 식사] moved - '''ToDo''' to '''Doing''' * [User:Aha00a] [2026-06-07]T21:27:13Z * ["#cmq4ajesg43bp1g" #아침 식사] updated - ParentCard - [#cmq4ajpa7tig43b 아침에 할일] * [User:Aha00a] [2026-06-07]T21:26:45Z * ["#cmq4ajesg43bp1g" #아침 식사] renamed - '''아침식사''' * [User:Aha00a] [2026-06-07]T21:25:17Z * ["#cmq4ajesg43bp1g" #아침식사] added ==== 아침 설거지 ==== #cmq4ajlewxrxne2 ===== Property * Creator: [User:Aha00a] * dateCreated: [2026-06-07]T21:25:25Z * ParentCard: [#cmq4ajpa7tig43b 아침에 할일] ===== Activity * [User:Aha00a] [2026-06-07]T21:28:48Z * ["#cmq4ajlewxrxne2" #아침 설거지] moved - '''Doing''' to '''Done''' * [User:Aha00a] [2026-06-07]T21:27:44Z * ["#cmq4ajlewxrxne2" #아침 설거지] moved - '''ToDo''' to '''Doing''' * [User:Aha00a] [2026-06-07]T21:27:24Z * ["#cmq4ajlewxrxne2" #아침 설거지] updated - ParentCard - [#cmq4ajpa7tig43b 아침에 할일] * [User:Aha00a] [2026-06-07]T21:26:39Z * ["#cmq4ajlewxrxne2" #아침 설거지] renamed - '''아침설거지''' * [User:Aha00a] [2026-06-07]T21:25:25Z * ["#cmq4ajlewxrxne2" #아침설거지] added
=== 리스트명: 컬럼==== 카드 제목 ==== #cardId: 카드===== Description: 카드 설명 (선택, 여러 줄, Activity에 밀리지 않음)===== Property: 카드 속성===== Activity: 활동 로그#...)는 생성 후 변경하지 않는다.409 Conflict 발생 시 최신 리비전을 재조회하여 최대 3회 자동 재시도한다. 모두 실패하면 화면 하단에 non-blocking 토스트를 표시하며 페이지는 새로고침하지 않는다.page.updated 이벤트를 수신하여 보드를 자동 갱신한다(실시간 동기화).다른 탭 또는 다른 사용자가 같은 Kanban 페이지를 저장하면 현재 탭의 보드가 자동으로 갱신된다.
page.updated 이벤트로 감지한다.page.updated 이벤트를 수신한다.GET /w/{page}?action=raw로 최신 원문을 가져온다.base — 가장 최근에 저장 성공한 시점의 칸반 상태local — 현재 로컬 in-memory 상태 (저장 중인 변경 포함)server — 방금 가져온 서버 상태로컬만 변경된 카드 → 로컬 버전 유지 서버만 변경된 카드 → 서버 버전 적용 양쪽 모두 변경된 카드 → 서버 우선 + 카드 표면에 노란 conflict 배지 로컬에만 추가된 카드 → 서버 상태에 병합하여 유지 모달에서 열린 카드 → 로컬 버전 유지 + 서버가 변경했으면 모달 상단 배너 표시
interpreterStartLine과 lineEnd를 서버 원문 기준으로 재설정한다.허용 속성:
Creator (1)DueDate (1)Assignee (n)BlockedBy (n)ParentCard (n)RelatedCard (n)Attachment (n)같은 InterpreterKanban 블록 안의 카드 관계를 Property로 표현한다.
관계 대상은 페이지명을 저장하지 않고 카드 ID hash만 저장한다.
===== Property * BlockedBy * [#card-api-spec API spec 확정] * ParentCard: [#card-login-epic Login Epic] * RelatedCard * [#card-design-review Design review]
[#cardId] 또는 [#cardId label] 형식을 사용한다.cardId이고, label은 원문 가독성을 위한 보조 정보로만 사용한다.InterpreterKanban 블록 내부로 제한한다.BlockedBy, ParentCard, RelatedCard를 직접 저장한다.Blocks, SubCards는 저장하지 않고 BlockedBy, ParentCard를 역방향으로 스캔해서 계산 표시한다.보드 뷰의 카드 표면에 관계가 있는 경우 관계 배지를 표시한다.
BlockedBy, ParentCard, RelatedCardBlocks, SubCardskanban-card-footer 안에 묶여, 내용이 있을 때만 구분선과 함께 표시된다.카드 표면 배지와 카드 상세 모달의 관계 Property 행에 동일한 색상 체계를 적용한다.
관계 타입 | 색상 |
|---|---|
BlockedBy | 빨간색 (error) |
Blocks (파생) | 주황색 (warning) |
ParentCard | 파란색 (primary) |
SubCards (파생) | 파란색 (primary) |
RelatedCard | 회색 (muted) |
카드 상세 모달에서는 관계 Property 행의 라벨과 카드 칩 테두리·배경에 위 색상을 적용한다.
openCardDetailById(cardId)로 대상 카드 모달을 연다.#cardId, cardId, [#cardId], [#cardId label]을 받아 내부 저장 형식으로 정규화한다.card:property:update로 저장한다.Assignee는 기존 Property 형식을 유지하되 카드 상세 모달에서 직접 편집할 수 있게 한다.
===== Property * Assignee * [User:Aha00a] * [User:Alice]
User:Aha00a, [User:Aha00a], Aha00a를 받아 [User:Aha00a]로 정규화한다.card:property:update로 저장하고 Activity에 기록한다.뷰포트 너비가 767px 이하이면 자동으로 모바일 모드로 전환된다.
window.matchMedia('(max-width: 767px)') 이벤트를 구독하여 리사이즈 시에도 동적으로 전환된다.
+ 버튼으로 리스트를 추가할 수 있다.card:move로 저장된다.#<cardId>로 설정한다.#<cardId>로 직접 진입하면 해당 카드 모달을 자동으로 열고 보드 위치로 이동한다.#<cardId>는 오류 없이 일반 보드 화면을 유지한다.DueDate, Assignee, 카드 관계를 직접 편집할 수 있다.형식: Kanban - [주체] 동사과거형 - Detail
Kanban - ["Kanban#cardId" Kanban#할일] moved - '''ToDo''' to '''Done'''Kanban - '''ToDo''' addedlist:add, list:rename, list:move, list:deletecard:add, card:rename, card:move, card:deletecard:comment:add, card:property:update, card:description:updatecard:comment:add는 코멘트 첫 줄 기준 최대 80자만 기록한다.card:rename의 Detail은 이전 제목(fromTitle). 카드링크 자체가 새 제목을 담고 있으므로 redundancy 없이 변경 내역을 표현한다.card:delete는 카드가 삭제되므로 Activity에는 별도로 기록하지 않는다.InterpreterKanban은 일반 위키 페이지와 같은 쓰기 권한 정책을 따른다.
페이지 렌더링 시 계산된 isWritable 값은 .wikiContent의 data-wiki-writable 속성으로 전달되고, Kanban UI는 이 값을 기준으로 root별 read-only 모드를 결정한다.
쓰기 권한이 없는 사용자는 Kanban 보드를 조회할 수 있지만, 내용을 변경하는 UI는 노출되지 않는다.
#<cardId> hash navigation은 동작한다.클라이언트의 read-only 처리는 UX를 위한 방어선이다.
실제 보안 경계는 서버 저장 로직의 /w/{page} POST 쓰기 권한 검사이며, 쓰기 권한이 없으면 403 Permission denied로 차단된다.
한 페이지에 여러 Kanban interpreter가 있을 수 있으므로 read-only 상태와 mutation 방어는 interpreter root별로 처리한다.
signed read URL로 페이지를 조회하는 경우에도 쓰기 권한이 없으면 read-only로 동작해야 한다.
Similar pages by cosine similarity. Words after page name are term frequency.