← Notes

슬랙 카드 UI의 물리 법칙은 문서에 없다

AI 에이전트 생태계 구축기· 12 / 14
  1. 1 — 완료는 실측으로만 말한다
  2. 2 — 같은 함정, 세 번째는 다른 얼굴로
  3. 3 — 테스트가 통과했다고 완료는 아니다
  4. 4 — 속 빈 강정 — 함수는 있는데 왜 안 불렸나
  5. 5 — 하루 만에 지은 나의 두 번째 뇌
  6. 6 — 조직보다 자산 먼저 — 두 번 실패하고 남은 3원칙
  7. 7 — AI의 판단력은 복리로 자라지 않는다
  8. 8 — 큐는 비우고, raw는 지우지 않는다
  9. 9 — 하나의 비서를 세 개의 뇌로 나눈 이유
  10. 10 — 허위 적격 0 — 애매하면 통과시키는 필터
  11. 11 — 텔레그램 단일 채팅에서 슬랙 3직원 사무실로 — 에이전트 비서진 이사 기록
  12. 12 — 슬랙 카드 UI의 물리 법칙은 문서에 없다
  13. 13 — 렌더러가 제목을 지어내고 있었다 — 같은 결정을 두 곳에 구현한 대가
  14. 14 — 장부는 누가 쓰는가

슬랙 Block Kit으로 카드 UI를 만들 때 제일 위험한 착각은 “문서에 임계값이 있으니 그대로 설계하면 된다”는 것이다. 내 보고·결재 표면을 슬랙 카드로 옮기면서 같은 착각을 두 번 반복했고, 결국 실제 채널에 5~10블록짜리 카드를 발사해 눈으로 임계를 쟀다.

슬랙 카드의 접힘 기준은 내가 쓴 방식에서는 글자 수가 아니라 attachment 하나에 들어간 블록 장수였다.

즉, 이 글은 예쁜 목업 이야기가 아니라 실측한 물리 법칙 목록이다. 문서가 틀렸다는 뜻이 아니라, 문서의 숫자가 어느 API 표면에 적용되는지 확인하지 않으면 설계가 엉뚱한 축을 세게 된다는 뜻이다.

접힘 임계는 블록 5장이다

처음에는 슬랙 문서의 “700자 또는 줄바꿈 5개” 규칙을 기준으로 카드 길이를 줄였다. 그런데 결재 버튼은 계속 “더 보기” 뒤로 숨었다. 줄바꿈을 세어 여백을 없애도 결과가 같았다.

그래서 추측을 멈추고 같은 형태의 카드를 여섯 장 만들었다.

  • 5블록 카드: 버튼이 보였다.
  • 6블록 카드: 접혔다.
  • 7~10블록 카드: 모두 접혔다.

결과는 단순했다. attachments 안의 blocks 방식에서는 attachment당 5블록까지가 안전선이고, 6번째 블록부터 접힘선 아래로 내려간다. 기준은 줄 수·글자 수가 아니라 블록 장수였다.

이 숫자는 최초 실패도 설명했다. 처음 실패한 카드는 킥커·제목·메타·그리드·경고까지 정확히 5장이 보였고, 6번째였던 버튼부터 잘렸다. 방증은 이미 눈앞에 있었는데, 내가 줄바꿈이라는 다른 축으로 세고 있었다.

긴 카드를 설계할 때는 본문 줄 수보다 attachment 안의 블록 수를 먼저 세야 한다.

컬러바를 지키면 접힘 임계를 안고 간다

카드 왼쪽의 세로 색줄은 포기하기 어려웠다. 상태를 한눈에 구분하게 해 주는 장치였고, 목업에서도 카드의 성격을 정하는 축이었다. 문제는 그 색줄이 attachments[].color로만 가능하다는 점이다.

선택지얻는 것잃는 것
top-level blocks접힘 임계에서 비교적 자유롭다왼쪽 컬러바를 잃는다
attachments[].blocks왼쪽 컬러바를 유지한다5블록 접힘 임계를 안고 간다

그래서 결론은 우회가 아니라 분할이었다. 같은 색 attachment를 여러 장으로 나누면 컬러바가 시각적으로 이어진다. 표준 형태는 아래처럼 잡았다.

  1. 본문 장: 제목·메타·요약·그리드 등, 5블록 이하.
  2. 처리 장: 버튼·처리 스탬프 등, 별도 attachment.

대가는 있다. attachment마다 “봇이 추가한 …” 식의 귀속 줄이 하나씩 붙는다. 제거할 수 없었다. 2단 분할이면 귀속 줄도 2개가 된다.

즉, 컬러바를 유지한다는 결정은 “접힘을 완전히 피한다”가 아니라 “5블록 단위로 잘라 귀속 줄을 감수한다”는 결정이다.

메시지 구조에는 보이지 않는 잉여줄이 생긴다

카드가 접히지 않게 만들려다 보니 메시지 구조의 다른 함정도 같이 드러났다. 특히 chat.postMessage의 top-level text는 내가 기대한 방식으로 숨지 않았다.

  • top-level text: attachment만 쓰는 메시지에서도 카드 위에 맨몸 본문 줄로 렌더됐다. 접근성용 요약처럼만 취급되지 않았다.
  • fallback·footer: blocks와 함께 넣으면 API가 invalid_keys로 거부했다. 귀속 줄을 footer로 대체하는 우회는 막혀 있었다.
  • 빈 텍스트 블록: 카드 사이 여백을 만들려고 빈 블록을 넣으면 거부됐다. 여백은 폭 없는 공백 문자를 담은 context 같은 방식으로 만들어야 했다.

이런 제약은 각각 사소해 보이지만, 카드 UI에서는 한 줄이 곧 접힘 임계와 직결된다. “안 보일 줄 알았던 줄” 하나가 생기면 버튼 위치가 바뀐다.

글씨와 색은 세 단계 안에서만 움직인다

HTML 목업에서는 제목 크기와 색을 마음대로 조절할 수 있다. 하지만 슬랙 메시지 안에서는 그 자유가 거의 없다.

요소실제 제약
큰 제목header 블록뿐이고 plain_text 전용이다
본문section 본문 크기에서 크게 벗어나지 못한다
작은 회색 글씨context 블록에 가깝고, 2열 정렬과 같이 쓰기 어렵다
텍스트 색mrkdwn에 색 문법이 없다
2열 그리드fields는 50:50 균등 분할 고정이다

header는 제목을 크게 만들 수 있지만 칩이나 인라인 코드를 품지 못한다. 제목 앞에 상태 칩을 붙이려면 제목을 section 볼드로 내려야 하고, 그러면 제목은 본문 크기가 된다. 둘 다 가질 수 없었다.

색도 마찬가지다. 초록 성공 문장, 빨간 경고 문장 같은 것은 mrkdwn 텍스트만으로는 만들 수 없다. 남는 인라인 색 신호는 이모지뿐이었다. 그래서 상태 도트는 장식이 아니라 제약 안에서 살아남은 색상 체계였다.

2열 그리드 역시 라벨을 좁게, 값을 넓게 나누는 식의 조절이 안 된다. fields는 50:50이다. 칸 안의 글씨를 작게 줄이는 것도 불가능했다. 작은 글씨는 context의 영역인데, context는 같은 방식의 2열 표가 아니다.

이모지는 다시 읽으면 숏코드가 된다

마지막 함정은 렌더링이 아니라 저장 방식에서 나왔다. 슬랙에 📋를 넣어 보냈다고 해서 API로 다시 읽을 때도 📋가 돌아오는 것이 아니었다. conversations.history로 재수집하면 :clipboard: 같은 숏코드 텍스트로 저장돼 있었다.

상태 전이 코드는 기존 카드의 킥커를 읽고 앞쪽 상태 표시를 갈아 끼운다. 여기서 리터럴 이모지만 지우도록 짜면 숏코드가 남는다. 실제로 상태 전이 뒤 킥커가 깨진 형태로 나타났다.

기존 메시지를 파싱하는 코드는 리터럴 이모지와 :shortcode: 양쪽을 모두 벗겨야 한다.

목업은 가능성 검증까지 포함해야 한다

이번 삽질의 결론은 “슬랙이 불편하다”가 아니다. 더 정확히는 목업을 그리는 순간, 그 목업이 플랫폼 위에서 가능한지까지 검증해야 한다는 것이다.

  • 문서 숫자의 적용 대상을 확인한다: legacy text 규칙인지, 현재 쓰는 blocks 규칙인지 분리한다.
  • 추측이 두 번 틀리면 계측한다: 5~10블록 카드를 직접 발사하는 비용이 계속 고치는 비용보다 작았다.
  • 실패 화면을 다른 축으로 다시 읽는다: 첫 실패에는 이미 “5블록까지만 보인다”는 힌트가 있었다.
  • 목업의 색·크기·그리드는 구현 가능성 표를 붙인다: HTML에서 되는 것과 Block Kit에서 되는 것은 다르다.

즉, 카드 UI의 품질은 꾸미는 능력보다 먼저 제약을 재는 능력에서 나온다. 슬랙에서는 특히 그렇다. 버튼이 접힘선 아래로 내려간 뒤에는, 예쁜 카드라는 말이 아무 의미가 없어진다.

참고 자료

  • [1] 슬랙 Block Kit 실측 물리 법칙 — 문서를 믿지 말고 자를 대라
  • [2] 슬랙 UI 측정과 우편 카드 데이터 바인딩 세션 증류
목차